Card
Token-variant cards as a div or a whole-card link, with theme and card-width figures.
On this page
A card is a padded, bordered box whose look comes from a card variant in your tokens. Use it for teasers, feature lists and pricing tiles, either as a plain container or as one large link.
Design tokens
Colors, spacing and type come from one Figma export, so every card on the site stays in sync with the design.
Read moreimport { Button, Card, Heading, Paragraph } from '@systhemaui/next'
export default function Demo() {
return (
<Card style={{ maxWidth: 360 }}>
<Heading.h3>Design tokens</Heading.h3>
<Paragraph>
Colors, spacing and type come from one Figma export, so every card on the site stays in sync
with the design.
</Paragraph>
<Button.a href="#examples" variant="secondary">
Read more
</Button.a>
</Card>
)
}ImportLink to this section
import { Card } from '@systhemaui/next'In a React app without Next.js, import it from @systhemaui/react.
Variants and tagsLink to this section
variant takes a card variant from your tokens: default and highlighted with the default tokens. Omit it to get the first variant. Each token variant becomes one class, card-<variant>, with its own padding, border, radius, shadow and colors.
Default
The everyday card for lists and teasers.
Highlighted
Draws the eye to one item in a group.
import { Card, Column, Columns, Heading, Paragraph } from '@systhemaui/next'
export default function Demo() {
return (
<Columns columns={2} gap="md" style={{ width: '100%' }}>
<Column>
<Card variant="default">
<Heading.h4>Default</Heading.h4>
<Paragraph>The everyday card for lists and teasers.</Paragraph>
</Card>
</Column>
<Column>
<Card variant="highlighted">
<Heading.h4>Highlighted</Heading.h4>
<Paragraph>Draws the eye to one item in a group.</Paragraph>
</Card>
</Column>
</Columns>
)
}The tag variants pick the element:
| Tag variant | Renders |
|---|---|
Card | <div>, or the element you pass as as |
Card.div | <div> |
Card.a | a link (in @systhemaui/next, a next/link; see Next.js) |
Card.link | @systhemaui/next only: the same next/link card as Card.a |
ExamplesLink to this section
ThemeLink to this section
theme sets data-theme on the card, so it renders in another color-system mode than the page around it. With the default tokens the modes are default and dark. See Themes and backgrounds.
Night shift
The card switches to the dark mode of the color system; its text and border tokens follow.
import { Card, Heading, Paragraph } from '@systhemaui/next'
export default function Demo() {
return (
<Card theme="dark" style={{ maxWidth: 360 }}>
<Heading.h4>Night shift</Heading.h4>
<Paragraph>
The card switches to the dark mode of the color system; its text and border tokens follow.
</Paragraph>
</Card>
)
}Linked cardsLink to this section
Use Card.a (or Card.link) when the whole card is the link, instead of wrapping a card in a separate link. A linked card gets the variant's hover colors and your transitionClasses.
import { Card, Column, Columns, Heading, Paragraph } from '@systhemaui/next'
export default function Demo() {
return (
<Columns columns={2} gap="md" style={{ width: '100%' }}>
<Column>
<Card.a href="#linked-cards">
<Heading.h4>Getting started</Heading.h4>
<Paragraph>Install the packages and render your first page.</Paragraph>
</Card.a>
</Column>
<Column>
<Card.a href="#linked-cards" variant="highlighted">
<Heading.h4>Components</Heading.h4>
<Paragraph>Browse every component with live examples.</Paragraph>
</Card.a>
</Column>
</Columns>
)
}Keep the content of a linked card free of other links and buttons: interactive elements nested in a link are invalid HTML and confuse keyboard and screen-reader users.
Card-width figuresLink to this section
A Figure with width="card" breaks out of the card's horizontal padding to its edges. As the first or last child it also drops the card's top or bottom padding, so media sits flush against the rounded corners.

Edge to edge
The figure ignores the card padding; the text keeps it.
import { Card, Figure, Heading, Image, Paragraph } from '@systhemaui/next'
export default function Demo() {
return (
<Card style={{ maxWidth: 360 }}>
<Figure width="card">
<Image
src="/demo-assets/coast.webp"
alt="A coastline at dusk"
width={1800}
height={1200}
sizes="360px"
/>
</Figure>
<Heading.h4>Edge to edge</Heading.h4>
<Paragraph>The figure ignores the card padding; the text keeps it.</Paragraph>
</Card>
)
}Rich text inside a cardLink to this section
A card carries the richtext class, so headings, paragraphs, lists and buttons inside it get the rich-text rhythm without extra margins. The card animates as one unit: animated elements inside it render in their end state without a reveal of their own. Pass disableAnimation to drop the card's own reveal as well.
PropsLink to this section
Card and Card.div accept as, variant, theme, disableAnimation and every attribute of the rendered element.
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | 'div' | |
className | string | - | |
variant | CardVariant (default, highlighted) | - | |
theme | ColorSystem (dark, default) | - | |
disableAnimation | boolean | - | |
…and all <div> attributes |
HTML and CSSLink to this section
Write the variant class plus richtext on any element. The variant classes are generated per token variant (card-default and card-highlighted with the default tokens), and their values come from the card tokens in spacing and colors.
<div class="card-highlighted richtext">
<h3 class="text-h3 color-heading">Design tokens</h3>
<p class="text-body color-body">Colors, spacing and type come from one Figma export.</p>
</div>
<a class="card-default richtext" href="/getting-started">
<h3 class="text-h3 color-heading">Getting started</h3>
<p class="text-body color-body">Install the packages and render your first page.</p>
</a>
<div class="card-default richtext" data-theme="dark">…</div>| Class | Styles |
|---|---|
card-default | :is(.richtext > :is(.columns, .stack, :is(.card-default), .card-highlighted, .accordion-default)):not(:first-child) { margin-top: calc(var(--typography-block-margin-y, 24px) * 2); }:is(.richtext > :is(.columns, .stack, :is(.card-default), .card-highlighted, .accordion-default)):not(:last-child) { margin-bottom: calc(var(--typography-block-margin-y, 24px) * 2); }+ 9 more rules |
figure-w-card | & { width: calc(100% + var(--card-width-offset-x, 0px) * 2); max-width: calc(100% + var(--card-width-offset-x, 0px) * 2); margin-inline: calc(var(--card-width-offset-x, 0px) * -1); border-radius: 0; }& :is(.media, .media-wrapper) { border-radius: 0; } |
w-card | & { width: calc(100% + var(--card-width-offset-x, 0px) * 2); max-width: calc(100% + var(--card-width-offset-x, 0px) * 2); margin-inline: calc(var(--card-width-offset-x, 0px) * -1); border-radius: 0; }& :is(.media, .media-wrapper) { border-radius: 0; } |
card-highlighted | :is(.richtext > :is(.columns, .stack, .card-default, :is(.card-highlighted), .accordion-default)):not(:first-child) { margin-top: calc(var(--typography-block-margin-y, 24px) * 2); }:is(.richtext > :is(.columns, .stack, .card-default, :is(.card-highlighted), .accordion-default)):not(:last-child) { margin-bottom: calc(var(--typography-block-margin-y, 24px) * 2); }+ 9 more rules |
Next.jsLink to this section
The @systhemaui/next card is its own implementation. Card.a and Card.link render through the LinkHelper, so the link is a next/link (with prefetching) and same-page hash links smooth-scroll. Passing as="a" takes the same route instead of rendering a bare <a>. Card.link is an alias that names the next/link rendering; it does not exist in @systhemaui/react, where Card.a is a plain anchor.
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | - | Optional decorator for the path that will be shown in the browser URL bar. Before Next.js 9.5.3 this was used for dynamic routes, check our previous docs (opens in new tab) to see how it worked. Note: when this path differs from the one provided in href the previous href/as behavior is used as shown in the previous docs (opens in new tab). |
className | string | - | |
variant | CardVariant (default, highlighted) | - | |
theme | ColorSystem (dark, default) | - | |
disableAnimation | boolean | - | |
rel | string | - | |
children | ReactNode | - | |
target | string | - | |
…and all InternalLinkProps props from next | |||
| …and all inherited HTML attributes |
AccessibilityLink to this section
- A card is a generic container with no role. Give it a heading so its content can be found by heading navigation.
- The accessible name of a linked card is all of its text. Keep that text short, and do not nest buttons or other links inside it.
- A linked card has no visible focus style of its own beyond the browser outline. Keep the outline, or add a
focus-visible:utility.
RelatedLink to this section
- Card block
- Figure
- Columns and Stack for card grids
- Themes and backgrounds