Skip to main content

Card

betav0.1.0

A composed surface built on Paper: header, title, subtitle, content, footer and a metadata strip, every part optional and combinable. What varies in structure composes; what varies in appearance is a root prop. Card declares no surface, typography or spacing of its own — the root is a Paper and every text goes through Text. Give it href or onClick and the whole card becomes one control whose accessible name is its title, while the buttons, chips and checkboxes inside it stay separate, reachable controls.

Variants

The defined card types

One per variant of the Figma set CardDefault (7233:50454). Copy the snippet as it stands — every control already carries size="sm", no icon button is primary, and no button is highlight.

Simple7233:50455
The recommended starting point: a thumbnail, a title and subtitle, a description clamped to three lines, then a tag and its actions.
Card title
Card subtitle or category
Lorem ipsum dolor sit amet. Lorem ipsum do lor sit amet, consectetur adipiscing elit. Nullam nec aliquam erat. Mauris fermentum enim dui, sed blandit eros imperdiet eu.
Active
<Card>
  <CardHeader
    icon={<Thumbnail><Icon name="server" size={24} /></Thumbnail>}
    action={<Icon name="users" size={24} aria-label="Shared" />}
  >
    <CardTitle>Card title</CardTitle>
    <CardSubtitle>Card subtitle or category</CardSubtitle>
  </CardHeader>
  <CardContent>Lorem ipsum dolor sit amet…</CardContent>
  <CardFooter>
    <Chip label="Active" severity="low" />
    <div className="flex items-center gap-2">
      <Button size="sm" priority="tertiary">Learn More</Button>
      <Button size="sm" priority="secondary">Deploy</Button>
    </div>
  </CardFooter>
</Card>
Entity7231:49799
A record in a grid. No thumbnail — the node hides it deliberately — and a block of labelled fields under the description.
Card title
Card subtitle or category
Lorem ipsum dolor sit amet. Lorem ipsum do lor sit amet, consectetur adipiscing elit. Nullam nec aliquam erat. Mauris fermentum enim dui, sed blandit eros imperdiet eu.
Known as:
-
Used Malware
Malware Name, Malware Name, Malware very long name
Targeted Countries:
Countries Name, Countries Name longue
Targeted Sectors:
Sector Name, Sector Name longue
windowscritical
<Card>
  <CardHeader action={<Icon name="flag" size={24} aria-label="Country" />}>
    <CardTitle>Card title</CardTitle>
    <CardSubtitle>Card subtitle or category</CardSubtitle>
  </CardHeader>
  <CardContent>Lorem ipsum dolor sit amet…</CardContent>
  {/* the field block is host-composed in v1 */}
  <CardFooter>
    <div className="flex items-center gap-2">
      <Chip label="windows" />
      <Chip label="critical" severity="critical" />
    </div>
    <IconButton icon={<Icon name="star" />} aria-label="Bookmark" size="sm" priority="tertiary" />
  </CardFooter>
</Card>
Sharing7233:50893
A one-line description, a status dot in the header slot, and a metadata strip pinned to the bottom edge. The node instantiates Badge in that slot, so the example uses Badge as it stands.
Card title
Card subtitle or category
Online
Lorem ipsum dolor sit amet. Lorem ipsum do lor sit amet, consectetur adipiscing elit. Nullam nec aliquam erat. Mauris fermentum enim dui, sed blandit eros imperdiet eu.
Active
Message:0
Last Seen:Aug 7, 2026, 11:34
<Card>
  <CardHeader
    icon={<Thumbnail><Icon name="server" size={24} /></Thumbnail>}
    action={<Badge dot tone="success" accessibleText="Online" />}
  >
    <CardTitle>Card title</CardTitle>
    <CardSubtitle>Card subtitle or category</CardSubtitle>
  </CardHeader>
  <CardContent clamp={1}>Lorem ipsum dolor sit amet…</CardContent>
  <CardFooter>
    <Chip label="Active" severity="low" />
    <IconButton icon={<Icon name="ellipsis-vertical" />} aria-label="Actions" size="sm" priority="tertiary" />
  </CardFooter>
  <CardMetaBar>
    <span>Message: 0</span>
    <span>Last Seen: Aug 7, 2026, 11:34</span>
  </CardMetaBar>
</Card>
Number7233:50994
A single figure with its trend and a thumbnail. The gutter is 16 here, which is what the node draws.
Entities+ 23%

12 480

<Card gap={16}>
  <div className="flex h-6 items-center gap-2">
    <Text as="span" variant="content-compact">Entities</Text>
    <Chip label="+ 23%" severity="low" />
  </div>
  <div className="flex items-center justify-between gap-4">
    <Text as="p" variant="title-2xl">12 480</Text>
    <Thumbnail><Icon name="server" size={24} /></Thumbnail>
  </div>
</Card>
Media7233:50672
A media band at the top edge, then the body. The structure is the answer to the asymmetric padding: Paper at padding 0, the band edge to edge at the node's fixed 150, and an inner content box carrying its own 16/24. No Paper API is extended — the content box is a plain container.
Card title
Card subtitle or category
Lorem ipsum dolor sit amet. Lorem ipsum do lor sit amet, consectetur adipiscing elit. Nullam nec aliquam erat. Mauris fermentum enim dui, sed blandit eros imperdiet eu.
Active
<Card padding={0} gap={0}>
  <img src={coverUrl} alt="" className="h-[150px] w-full shrink-0 object-cover" />
  <div className="flex min-w-0 flex-col gap-6 px-6 py-4">
    <CardHeader icon={<Thumbnail><Icon name="newspaper" size={24} /></Thumbnail>}>
      <CardTitle>Card title</CardTitle>
      <CardSubtitle>Card subtitle or category</CardSubtitle>
    </CardHeader>
    <CardContent clamp={1}>Lorem ipsum dolor sit amet…</CardContent>
    <CardFooter>
      <Chip label="Active" severity="low" />
      <div className="flex items-center gap-2">
        <IconButton icon={<Icon name="thumbs-up" />} aria-label="Like" size="sm" priority="tertiary" />
      </div>
    </CardFooter>
  </div>
</Card>

Composing your own card

The parts are optional and combinable, in any order. When no defined type fits, compose header, content and footer yourself.

Simple without its content — the short cardrecipe
Drop CardContent and the same composition becomes the compact card. No variant needed: a card with no content is valid.
Card title
Card subtitle or category
windowscritical
<Card>
  <CardHeader
    icon={<Thumbnail><Icon name="bug" size={24} /></Thumbnail>}
    action={<Checkbox aria-label="Select this card" />}
  >
    <CardTitle>Card title</CardTitle>
    <CardSubtitle>Card subtitle or category</CardSubtitle>
  </CardHeader>
  <CardFooter>
    <div className="flex items-center gap-2">
      <Chip label="windows" />
      <Chip label="critical" severity="critical" />
    </div>
  </CardFooter>
</Card>
A navigable card that keeps its own controlsarbitration
Give the root href and the anchor lands on the title, stretched over the surface. One tab stop for the card, one per control, and the card is named by its title.
Intrusion set
<Card href={`/threats/${id}`}>
  <CardHeader
    icon={<Thumbnail><Icon name="bug" size={24} /></Thumbnail>}
    action={<IconButton icon={<Icon name="bookmark" />} aria-label="Bookmark" size="sm" priority="tertiary" />}
  >
    <CardTitle>APT29</CardTitle>
    <CardSubtitle>Intrusion set</CardSubtitle>
  </CardHeader>
  <CardFooter>
    <Chip label="apt" onClick={filter} />
    <Button size="sm" priority="secondary">Deploy</Button>
  </CardFooter>
</Card>
Header onlyrecipe
Every part is optional. A header on its own is a valid card.
Card title
<Card padding={16}>
  <CardHeader icon={<Thumbnail><Icon name="shield" size={24} /></Thumbnail>}>
    <CardTitle>Card title</CardTitle>
  </CardHeader>
</Card>

A card takes the width it is given

No intrinsic width, no height. The same card sits in a narrow column and in a full-width row; the title and subtitle truncate at the track edge and keep the full text on the tooltip.

An extremely long card title that will not fit
And a subtitle every bit as long
The same card, in the rest of the row
It fills whatever the parent gives it

Examples

<Card href="/threats/apt29">
  <CardHeader action={<IconButton icon={<Icon name="bookmark" />} aria-label="Bookmark" size="sm" priority="tertiary" />}>
    <CardTitle>APT29</CardTitle>
    <CardSubtitle>Intrusion set</CardSubtitle>
  </CardHeader>
  <CardContent clamp={3}>Attributed to a state-sponsored group.</CardContent>
  <CardFooter>
    <Chip label="apt" />
    <Button size="sm" priority="secondary">Deploy</Button>
  </CardFooter>
</Card>
<Card padding={16}>
  <CardHeader icon={<Paper padding={8} className="flex size-12 items-center justify-center"><Icon name="newspaper" size={24} /></Paper>}>
    <CardTitle>Weekly report</CardTitle>
  </CardHeader>
</Card>
<Card selected>
  <CardHeader action={<Checkbox checked aria-label="Select" />}>
    <CardTitle>Credential dumping</CardTitle>
  </CardHeader>
</Card>

Props

PropType
elevation0 | 1 | 2 | 3 — passed through to Paper. Default 1.
padding0 | 8 | 16 | 24 | 32 (px) — passed through to Paper. Default 24.
gap0 | 8 | 16 | 24 | 32 (px) — the gutter between the parts. Default 24, the node's value everywhere except the statistic composition, which draws 16.
hrefstring — makes the whole card navigable. The anchor is rendered by CardTitle and stretched over the surface, so the card is one control named by its title. Mutually exclusive with onClick.
onClickhandler — same mechanism as href, as a button. Mutually exclusive with href.
selectedboolean — repaints the surface with the hover couple. The selection itself is carried by a Checkbox in the header action slot; the surface reinforces it.
disabledboolean — only meaningful on an interactive card. Renders the title as text.
CardHeader.iconReactNode — leading slot. The node draws a 48x48 Paper holding a 24px Icon.
CardHeader.actionReactNode — trailing slot: an Icon, an IconButton, a Checkbox. Free slot.
CardTitle.ash2 | h3 | h4 | h5 | h6 | div — no heading level is applied by default.
CardTitle.endAdornmentReactNode — trailing content on the title line.
CardSubtitle.endAdornmentReactNode — trailing content on the subtitle line.
CardContent.clampnumber — clamp the text to N lines. Defaults to 3, the node's 56px box (3 lines of 14px/1.5, half-leading trimmed). Pass 0 to let the content grow.

Accessibility

2.1 AAWCAG pass

The card resolves to ONE control: the anchor or button lives on the title and is stretched over the surface by a pseudo-element, so the accessible name is the title rather than the concatenation of the card's text, the markup nests no interactive element, and every inner control keeps its own tab stop. The root generates the title id. Hover is driven by :has() on that control, so pointing at an inner control drops the card highlight. The focus ring is the library's standard pattern and is a declared divergence from the node, which draws no focus state (Card.figma.json). Disabled follows WCAG 1.4.3 note 1: the surface does not change and only the text ramp moves, which is why no disabled pair is declared above. Selected paints the same couple as hover by design — the Checkbox carries the selection.