Skip to content
Entangle UI v0.13.0

Card

A bounded surface representing a single semantic unit — an asset, a summary, a preview, a list row. Card is distinct from Paper (a generic surface): it ships with a header / media / body / footer convention and can be made interactive by passing onClick, giving the whole card a button role with keyboard support.

Live Preview

  • Card — one self-contained item (an asset, a preset, a result). Has a fixed slot grammar (header / media / body / footer) and can become a clickable target.
  • Paper — a generic raised / bordered surface with no semantics. Use when you just need “a box”.
  • PanelSurface — a structural panel inside an editor shell (header + scrollable body + footer). Anchored, not a discrete item.

Reach for Card when you’re rendering a collection of discrete units — an asset grid, a list of presets, a feed of results. Reach for Paper when you just need a styled container.

import { Card } from 'entangle-ui';

The compound members are attached to Card:

<Card>
<Card.Header>...</Card.Header>
<Card.Media>...</Card.Media>
<Card.Body>...</Card.Body>
<Card.Footer>...</Card.Footer>
</Card>
<Card variant="elevated">
<Card.Header title="Asphalt 02" subtitle="Updated 2 hours ago" />
<Card.Media src="/preview.png" alt="Preview" aspectRatio={16 / 9} />
<Card.Body>4K PBR material with roughness and normal maps.</Card.Body>
<Card.Footer align="space-between">
<span>12.4 MB</span>
<Button variant="filled">Open</Button>
</Card.Footer>
</Card>

All compound members are optional and can appear in any order — though the canonical order (header → media → body → footer) reads best in most layouts.

variant chooses the surface treatment. outlined is the default.

Variants

| Variant | Treatment | Use when | | ---------- | ------------------------------------ | ------------------------------------------------ | | outlined | Transparent background, 1px border | Default. Quiet card that sits on any surface. | | filled | Surface-tinted background, no border | Cards on the canvas background; gives mild lift. | | elevated | Surface background plus drop shadow | Interactive cards (asset grids, result lists). |

<Card variant="outlined">...</Card>
<Card variant="filled">...</Card>
<Card variant="elevated">...</Card>

Pass onClick to turn the whole card into a button: role="button", tabIndex=0, Enter and Space activation, and a focus ring on :focus-visible. Combine with selected for grid-style selection.

Interactive

const [picked, setPicked] = useState<string | null>(null);
<Card
variant="elevated"
onClick={() => setPicked(item.id)}
selected={picked === item.id}
>
<Card.Header title={item.title} subtitle={item.subtitle} />
</Card>;

selected cards receive the accent border and a tinted background; the component also sets aria-pressed="true" when interactive and selected.

Card.Media renders edge-to-edge inside the card and respects its own aspect ratio. Pass src for the common <img> case, or drop arbitrary children (video, canvas, <picture>) for everything else.

With media

<Card variant="elevated">
<Card.Media src="/preview.png" alt="Preview" aspectRatio={16 / 9} />
<Card.Header title="Lobby render" subtitle="Saved 5 min ago" />
<Card.Body>Description...</Card.Body>
</Card>

The default aspect ratio is 16 / 9. Pass any number to override (e.g. aspectRatio={1} for a square thumbnail, aspectRatio={4 / 3} for legacy footage). Numeric aspectRatio is interpreted as width / height.

For dense lists (file rows, search results, asset rows) use outlined cards with Card.Header only — leading carries the icon and trailing carries the affordance.

List rows

<Card variant="outlined" onClick={open}>
<Card.Header
leading={<FolderIcon />}
title="Concrete 04"
subtitle="PBR material — 8.2 MB"
trailing={
<Text size="xs" color="muted">
Open
</Text>
}
/>
</Card>

disabled dims the card, drops pointer-events, and sets aria-disabled="true". It also short-circuits onClick, so a disabled card never becomes interactive.

Disabled

<Card variant="elevated" disabled onClick={open}>
<Card.Header title="Locked preset" subtitle="Pointer events off" />
<Card.Body>Body text is dimmed.</Card.Body>
</Card>

Card.Footer is a flex row with a top border. align controls horizontal placement.

Footer alignment

<Card.Footer align="left">...</Card.Footer>
<Card.Footer align="center">...</Card.Footer>
<Card.Footer align="right">...</Card.Footer>
<Card.Footer align="space-between">...</Card.Footer>

right is the default — fits the “Cancel + Confirm” button pattern most cards end with.

When children is passed to Card.Header, the default title/subtitle layout is bypassed and your nodes are rendered in the text column instead. leading and trailing still render in their respective columns.

Custom header

<Card.Header>
<Flex justify="space-between" align="center">
<Text weight="semibold">Custom header layout</Text>
<Text size="xs" color="muted">
Free-form children
</Text>
</Flex>
</Card.Header>

Card is a compound component. The members below are attached to Card (Card.Header, Card.Media, Card.Body, Card.Footer) and are all optional — pick the ones you need.

| Member | Role | | ------------- | --------------------------------------------------------------------------- | | Card | Root surface. Owns variant, selection, disabled state, and interactivity. | | Card.Header | Title / subtitle row with optional leading and trailing slots. | | Card.Media | Edge-to-edge image (via src) or arbitrary children at a fixed aspect. | | Card.Body | Descriptive body copy. Flex-grows so the footer stays pinned to the bottom. | | Card.Footer | Action row with a top border and configurable alignment. |

All members accept their own ARIA / data attributes (passed through to the underlying div), so you can tag any slot for testing or screen-reader hints without wrapping.

  • Static cards render as a <div> with no role; they participate in the document outline through their children.
  • When onClick is set and the card is not disabled, the root receives role="button", tabIndex=0, and a Space / Enter keyboard handler. aria-pressed reflects selected.
  • When disabled is set, the card sets aria-disabled="true" and short-circuits onClick; the keyboard handler is not attached.
  • Focus state uses :focus-visible so keyboard users see the focus ring without it appearing on mouse clicks.
  • Card.Header renders its title as <h3> and its subtitle as <p> — pick custom children when the heading level needs to differ.
  • Card.Media requires alt whenever src is set; pass an empty string for purely decorative imagery.
Prop Type Default Description
variant 'outlined' | 'filled' | 'elevated' 'outlined' Visual variant.
onClick (event: MouseEvent<HTMLDivElement>) => void — Click handler. When set, the whole card becomes interactive (button role, keyboard activation, focus ring).
selected boolean false Selected state — applies the accent border and tint. Combine with `onClick` for grid-style selection.
disabled boolean false Disabled state. Dims the card, removes pointer events, and short-circuits `onClick`.
children ReactNode — Card content — typically `Card.Header`, `Card.Media`, `Card.Body`, and/or `Card.Footer`.
Prop Type Default Description
title ReactNode — Title rendered as `<h3>`. Ignored when `children` is provided.
subtitle ReactNode — Subtitle rendered as `<p>` below the title. Ignored when `children` is provided.
leading ReactNode — Leading visual (icon, Avatar) rendered to the left of the text column.
trailing ReactNode — Trailing slot (IconButton, Menu) rendered to the right of the text column.
children ReactNode — Custom content for the text column. Overrides the default title/subtitle layout when provided.
Prop Type Default Description
src string — Image source — shortcut for rendering an `<img>` inside the media slot.
alt string — Alt text for the image. Defaults to an empty string when omitted; pass an empty string explicitly for decorative imagery.
aspectRatio number 16 / 9 Aspect ratio of the media slot (width / height). Use `1` for square thumbnails, `4 / 3` for legacy footage.
children ReactNode — Arbitrary children rendered inside the media slot. Used when `src` is not set — drop in a `<video>`, `<canvas>`, or `<picture>`.
Prop Type Default Description
children ReactNode — Body content. Flex-grows so the footer stays pinned to the bottom of the card.
Prop Type Default Description
align 'left' | 'center' | 'right' | 'space-between' 'right' Horizontal alignment of footer content.
children ReactNode — Footer content — typically action buttons.