Skip to content

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

VariantTreatmentUse when
outlinedTransparent background, 1px borderDefault. Quiet card that sits on any surface.
filledSurface-tinted background, no borderCards on the canvas background; gives mild lift.
elevatedSurface background plus drop shadowInteractive 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>

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.

MemberRole
CardRoot surface. Owns variant, selection, disabled state, and interactivity.
Card.HeaderTitle / subtitle row with optional leading and trailing slots.
Card.MediaEdge-to-edge image (via src) or arbitrary children at a fixed aspect.
Card.BodyDescriptive body copy. Flex-grows so the footer stays pinned to the bottom.
Card.FooterAction 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 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.

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.