Docs
Systhema Design (opens in new tab)
Unreleased

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.

Card

Design tokens

Colors, spacing and type come from one Figma export, so every card on the site stays in sync with the design.

Read more
import { 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.

Card variants

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 variantRenders
Card<div>, or the element you pass as as
Card.div<div>
Card.aa 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.

Dark card on a light page

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.

Whole-card links
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.

Figure flush to the card edges
A coastline at dusk

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.

PropTypeDefaultDescription
asElementType'div'
classNamestring-
variantCardVariant (default, highlighted)-
themeColorSystem (dark, default)-
disableAnimationboolean-
…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>
ClassStyles
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.

PropTypeDefaultDescription
asElementType-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).
classNamestring-
variantCardVariant (default, highlighted)-
themeColorSystem (dark, default)-
disableAnimationboolean-
relstring-
childrenReactNode-
targetstring-
…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.