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
When to use
- 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
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>Usage
<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.
Variants
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>Interactive
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.
With media
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.
List-item layout
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
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>Footer alignment
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.
Custom header content
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>Compound API
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.
Accessibility
- Static cards render as a
<div>with no role; they participate in the document outline through their children. - When
onClickis set and the card is notdisabled, the root receivesrole="button",tabIndex=0, and a Space / Enter keyboard handler.aria-pressedreflectsselected. - When
disabledis set, the card setsaria-disabled="true"and short-circuitsonClick; the keyboard handler is not attached. - Focus state uses
:focus-visibleso keyboard users see the focus ring without it appearing on mouse clicks. Card.Headerrenders itstitleas<h3>and itssubtitleas<p>— pick custom children when the heading level needs to differ.Card.Mediarequiresaltwheneversrcis set; pass an empty string for purely decorative imagery.
API Reference
<Card>
| 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`. |
<Card.Header>
| 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. |
<Card.Media>
| 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>`. |
<Card.Body>
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Body content. Flex-grows so the footer stays pinned to the bottom of the card. |
<Card.Footer>
| Prop | Type | Default | Description |
|---|---|---|---|
align | 'left' | 'center' | 'right' | 'space-between' | 'right' | Horizontal alignment of footer content. |
children | ReactNode | — | Footer content — typically action buttons. |