Card Documentation has not yet been reviewed by a human.
A surface container: a bordered, toned box that groups related content. The
Card JSX wrapper renders an <a-card>, which lays out an optional full-bleed
media region plus a header / body / footer stack. Pass href to turn the
whole card into a link.
A card stylizes only its own surface, never its content’s typography. Pass
header and body (children) as strings and the wrapper wraps them in a
<Title> and a <Text> that follow the card’s tone and size; pass your own
nodes to take over. It shares the tone / priority / size vocabulary with the
rest of Anta.
Playground
Anatomy
A card is three stacked sections — header, body (children), footer — plus an
optional full-bleed media region. Each is optional; omit unused regions. Spacing comes from one --card-padding: the outer inset and the body→footer
gap are a full --card-padding, and the header sits a half-step closer to its body
(--card-padding / 2). A string header becomes a <Title> and a string body a
<Text>; pass your own node (<Title>, <Text>, a layout) to take over. An
optional subtitle renders under the title as smaller text (<Text size="small">). There’s
no dedicated actions region: lay out header controls (a button, a tag) inside the
header itself.
<Card media={<img src="/hero.jpg" alt="" />} icon="book-open" {/* a string shape → an <Icon> in a circular chip */} header="Card title" subtitle="A short supporting subtitle" footer={<><Button priority="secondary" label="Launch" /><Button priority="tertiary" label="Edit" /></>}> The body sits under the header. Every section is optional.</Card>
{/* mediaPosition moves the media to another edge */}<Card media={<img src="/hero.jpg" alt="" />} mediaPosition="left" header="Card title" footer={<><Button priority="secondary" label="Launch" /><Button priority="tertiary" label="Edit" /></>}> The same four zones, with the media bleeding to the left edge instead of the top.</Card>Priority
priority sets the surface. primary (the default) is a clean sheet — bg-1
in light mode, bg-4 in dark (and a toned card’s cleanest tint: bg-2-{tone}
light, bg-4-{tone} dark); secondary is a subtle fill (bg-2, or the deeper
tint for a toned card); tertiary is a frosted panel: the primary surface
made semi-transparent with a backdrop blur, so it reads over busy, colored, or
image backgrounds (the blur shows where there’s detail behind). All three carry
a hairline ring — border-4 for a neutral card, or the tone’s border-4-{tone}
when toned (stepping to border-3 on hover).
–bg-1 light, –bg-4 dark).–bg-2).<Card header="Primary (default)">…</Card> {/* primary is the default */}<Card priority="secondary" header="Secondary">…</Card><Card priority="tertiary" header="Tertiary">…</Card>Tone
A semantic tone tints the surface, border, and text. The set matches the rest of
Anta: neutral (the default) plus brand, info, success, warning, and
critical. Color comes from the theme tokens, so every tone tracks light and dark
mode. A toned card is how you build a banner or callout — a filled card
with an icon and a message (add a footer for an action):
{/* A string `icon` gets the circular chip; pass a bare <Icon> node for a plain tinted glyph, as these banners do. */}<Card tone="info" priority="primary" icon={<Icon shape="info" />} header="Heads up">…</Card><Card tone="success" priority="primary" icon={<Icon shape="circle-check" />} header="All checks passed">…</Card><Card tone="warning" priority="primary" icon={<Icon shape="warning-triangle" />} header="Approaching quota">…</Card><Card tone="critical" priority="primary" icon={<Icon shape="warning-diamond" />} header="Deployment failed">…</Card>tone also accepts any literal CSS color for a one-off tint — the hue is kept
while lightness and chroma are pinned to the named-tone curve (and re-tuned for
dark mode), the same mechanism as Button / Expander / Tag. The surface,
border, and the <Title> / <Text> the wrapper adds all derive from the color —
Title and Text take a custom tone too, so a string header / body tints to
match:
<Card tone="#ff1493" priority="primary" header="Magenta">…</Card><Card tone="rebeccapurple" priority="primary" header="Purple">…</Card><Card tone="#0d9488" priority="primary" header="Teal">…</Card>Size
size scales the padding, the auto-wrapped body text, and the icon chip —
small, medium (the default), large.
<Card size="small" header="Small">…</Card><Card header="Medium (default)">…</Card> {/* medium is the default */}<Card size="large" header="Large">…</Card>Round
Omit round to use the theme’s corner radius. Pass a number (px) or CSS length
to set a fixed radius; round={0} makes the corners square. Bare round makes
the card fully round, clamped to its box.
round={16}.round=“0”.<Card round={16} header="Custom radius">…</Card><Card round="0" header="Square">…</Card>Header controls
There is no dedicated actions region. To place a control in the top-right of the
header, pass a layout node as header. For example, use a flex row containing
the title and a “more” button. That layout determines where both items appear.
<Card header={ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}> <Title level={4}>Production database</Title> <Button size="small" priority="tertiary" icon="more" aria-label="More" /> </div> }> Region us-east-1 · 4 vCPU · 16 GB</Card>Footer
footer is a left-aligned row (it wraps under pressure) — the opposite of
Dialog’s right-aligned footer, since a card reads as left-aligned content rather
than a decision surface. Pass several buttons as a fragment.
<Card header="Invite teammates" footer={<><Button priority="primary" size="small" label="Send invites" /><Button size="small" label="Copy link" /></>}> <Text>They'll get access to this project and its test runs.</Text></Card>Media
Pass media for a full-bleed image or illustration — it escapes the card padding
and clips to the corners. mediaPosition places it on the top (the default),
bottom, left, or right edge.
Top (default)
Left
<Card media={<img src="/hero.jpg" alt="" />} header="Top media">…</Card> {/* top is the default */}<Card media={<img src="/hero.jpg" alt="" />} mediaPosition="left" header="Left media">…</Card>{/* mediaPosition also takes "bottom" and "right" */}Link card
Give a card an href and the whole thing becomes a link — a single focusable
anchor, keyboard-activatable, with the hover lift and inset focus ring. The
default accessible name uses a text header or body. Custom nodes use their native
content name; pass aria-label or aria-labelledby to choose a shorter name. A
link card is display content: keep interactive controls (buttons, nested links)
out of it — its text is non-selectable and the whole surface navigates.
<Card href="/docs/ci" header={<><Icon shape="book-open" /> CI integration guide</>}> Connect your CI pipeline in a few minutes.</Card>Set target / rel for a new-tab link (target="_blank" rel="noopener"), or
aria-label to override the derived link name.
Selected
selected draws an inset ring in the tone color — for choice cards, plan pickers,
and selected list items.
<Card selected tone="brand" priority="primary" header="Team plan">…</Card><Card header="Free plan">…</Card>Loading
loading dims the card with a skeleton pulse and sets aria-busy. On a link card
it also blocks navigation until the load resolves.
<Card loading header="Loading…"> <Text>Content is being fetched.</Text></Card> Component props
| Prop | Type | Default | Description |
|---|---|---|---|
| header? | ReactNode | — | Header content, in the top zone. A string is wrapped in a <Title> — a real
heading in the document outline, tinted to match tone, at a level that
tracks size (small → 5, medium → 4, large → 3). Pass your own node
(<Title level={2}>, an <h2>) for a different level or styling. Omit for
no header. |
| subtitle? | ReactNode | — | A secondary line under the header title, rendered as smaller text
(<Text size="small">) in the heading section. Tinted to match tone, like
the title. Omit for no subtitle. |
| icon? | stringstringnumberbigintbooleanReactElementIterableReactPortalPromisenull | — | Leading visual for the header, laid out to the left of the header /
subtitle as one aligned row (so it doesn't sit inline in the title, where it
mis-aligns). A string is an icon shape, rendered as an <Icon> inside a
circular chip (sized per the card via --card-icon-size); pass an <img>,
initials (<Text>), or any node to use it as-is. Omit for none. |
| footer? | ReactNode | — | Footer content, usually action buttons. Rendered as a left-aligned row
(wraps under pressure) — the opposite of Dialog's right-aligned footer. |
| media? | ReactNode | — | Media (image, illustration) rendered full-bleed to the card edge, clipped to
its corners. Position it with mediaPosition. |
| mediaPosition? | topbottomleftright | 'top' | Which edge the media bleeds to. |
| children? | ReactNode | — | The card body. A string is wrapped in a <Text> at secondary priority,
following the card's tone and size; pass your own nodes to take over. |
| tone? | neutralbrandcriticalinfosuccesswarningstring | 'neutral' | Semantic tone, or any literal CSS color ('#ff1493', 'rebeccapurple') for
a one-off custom tone. Named tones re-point the surface + text; a custom
color keeps its hue while lightness/chroma are pinned. 'neutral' (the
default) is the same as omitting it. |
| priority? | primarysecondarytertiary | 'primary' | Surface emphasis. primary (the default) is a clean sheet; secondary is a
subtle fill; tertiary is a frosted, semi-transparent panel with a backdrop
blur. |
| size? | smallmediumlarge | 'medium' | Size variant — scales the padding. |
| selected? | boolean | — | Selected / chosen state — draws an inset ring in the tone color, for choice-card / plan-picker patterns. |
| loading? | boolean | — | Loading state — dims the card with a skeleton pulse, sets aria-busy, and (in
link mode) blocks navigation. |
| round? | booleannumberstring | — | Fully-round corners (border-radius: 999px, clamped to the box). Pass a
number (px) or a CSS length string ('1rem') for a custom radius. Omit for
the default --card-radius. |
| href? | string | — | Turn the whole card into a native link. ARIA props and tabIndex apply to
that anchor. A text header or body supplies its default name; custom content
uses the native content name. Override with aria-label or aria-labelledby.
Linked cards must not contain interactive controls. |
| target? | string | — | Anchor target (only with href). |
| rel? | string | — | Anchor rel (only with href). |
| ping? | string | — | Space-separated URLs the browser pings on navigation (only with href). |
Inherited props (className, id, slot, style, tabIndex, title)
| Prop | Type | Default | Description |
|---|---|---|---|
| className? | string | — | CSS class on the component's root element (merged with the component's own
classes). Use it directly for layout and positioning — grid/flex placement,
margins, alignment — rather than wrapping the component in a <div>/<span>. |
| id? | string | — | HTML id attribute. |
| slot? | string | — | Assigns the element to a named <slot> of a parent web component (e.g.
slot="header" inside a <Dialog>, slot="footer" inside a <Dialog>). |
| style? | CSSProperties | — | Inline styles on the component's root element. Set layout/positioning here
(or via className) directly on the component instead of adding a wrapper. |
| tabIndex? | number | — | Tab order. Set to -1 to skip the element when tabbing. |
| title? | string | — | HTML title attribute — native browser tooltip on hover. |
Plus every standard DOM event handler (onClick,
onFocus, onKeyDown, …) and any
data-* or aria-* attribute — forwarded as-is to
the underlying <a-*> element.
HTML
Use the same structural tags in raw HTML. Card has no element class or registration
step. Omit unused regions. For a linked card, put its media and content inside
a native anchor directly under a-card. Raw authors own that anchor’s URL, ARIA,
and loading updates.
<a-card> <a-card-content> <a-card-header> <a-card-title> <a-title level="5" role="heading" aria-level="5">Release notes</a-title> </a-card-title> </a-card-header> <a-card-body> <a-text>Put body content inside a-card-body.</a-text> </a-card-body> <a-card-footer> <a-button role="button" tabindex="0" size="small"> <a-button-label>Read more</a-button-label> </a-button> </a-card-footer> </a-card-content></a-card>Migrate from shadow parts
The JSX props remain available. Card now emits structural tags instead of flat
slot markup. Raw HTML authors must add those regions explicitly. Replace
::part(body) with a selector for a-card-body, and make the same replacement
for the other regions. Direct child paths keep an outer Card’s overrides from
reaching a nested Card.
Navigation attributes belong on a native <a data-anta-card-link> directly
under a-card. Its a-card-layout child arranges media and content independently
of generic link display rules. JSX creates this structure automatically. Raw
link Cards use:
<a-card> <a data-anta-card-link href="/guide"> <a-card-layout> <a-card-content> <a-card-body>Read the guide</a-card-body> </a-card-content> </a-card-layout> </a></a-card>Changing href or loading on a raw a-card no longer updates a link. The JSX
wrapper renders those updates from props. aria-* props and tabIndex apply to
the anchor in link mode and to a-card otherwise. A text header or body supplies
the default link label; custom content uses its native accessible name unless you
provide aria-label or aria-labelledby.
ACardElement and register_a_card are removed. The granular element entry loads
CSS only; bundle usage still pairs JavaScript and CSS for other Anta components.
Styling
Reach for the props first: tone sets the color (any CSS color for a custom
tone — it derives the surface in oklch), priority the fill, size the
padding. The one custom-color knob worth keeping is --card-tone-source (the
color a custom tone derives from — set it to drive the palette from your own
variable). The corner radius is --card-radius.
<Card tone="#e0457b" header="Custom" />Header heading level. A string header becomes a <Title> whose level tracks
size — small → 5, medium → 4, large → 3 — a real heading in the document outline.
For a different level or your own styling, pass a <Title level={n}> (or an
<h2>–<h6>) as the header; the card zeroes its top margin so it spaces the same
either way:
<Title>, in the page outline.<Card header={<Title level={2}>Heading semantics</Title>}> The header is a level-2 heading node, in the page outline.</Card>Card’s layout, focus ring, selected ring, and loading animation use external CSS
and work with style-src 'self'. Custom tones and custom radii can produce inline
styles in server-rendered JSX and need separate CSP handling.
Card renders its complete light-DOM structure immediately. Its regions are
a-card-layout in link mode, a-card-media, a-card-content, a-card-header,
a-card-icon, a-card-title, a-card-body, and a-card-footer. Use child selectors to style
these tags. Card needs no shadow parts or pre-upgrade layout.
Each example keeps its CSS in a <style> inside its own preview, targeting a
demo class on that preview’s card — so the rule you read is literally in the
preview’s DOM, next to the element it styles. In your app, swap the demo class for
your own selector.
Scroll a capped body. The card never scrolls, and the body doesn’t by default.
When you cap the card’s height and want the body to scroll instead of the card
growing, that’s one line on a-card-body:
/* cap the card's height, then let the body scroll */a-card.scrollable { height: 160px; }a-card.scrollable > a-card-content > a-card-body { overflow: auto; }Separate the header with a divider by styling a-card-header:
a-card.divided > a-card-content > a-card-header { padding-bottom: 8px; border-bottom: 1px solid var(--border-5);}