Skip to content

Card

A surface that groups a title, a description and related content. With a link as its root, the whole card is one link.

Live set

Recorded on 12 October.

Two stereo stems and a click track.
import { Card } from '@contour-ui/react';

<Card.Root>
  <Card.Header>
    <Card.Title>Live set</Card.Title>
    <Card.Description>Recorded on 12 October.</Card.Description>
  </Card.Header>
  <Card.Body>Two stereo stems and a click track.</Card.Body>
  <Card.Footer>
    <span>12 tracks</span>
    <span>48 kHz</span>
  </Card.Footer>
</Card.Root>;

Every part is optional. For a card with a title and a description only, put both in Card.Body.

Variants

Outline

A border around the surface.

Subtle

The surface alone.

Elevated

A raised surface with a shadow.

A subtle card keeps a transparent border, so cards of every variant have the same size. size sets the padding and the gap between the parts.

Pass a link to Card.Root with asChild, and the whole card is that link, with a hover color and a focus ring. Use your router's link in place of <a>:

import { Link } from 'react-router';

<Card.Root asChild>
  <Link to="/docs/essentials">
    <Card.Body>
      <Card.Title>Essentials</Card.Title>
      <Card.Description>The everyday components of contour-ui.</Card.Description>
    </Card.Body>
  </Link>
</Card.Root>;

A <button> works the same way and fills its container like any card.

Anatomy

PartElementClassAttributes
Card.Rootdivcontour-carddata-scope="card" data-part="root", data-variant, data-size
Card.Headerdivcontour-card__headerdata-scope="card" data-part="header"
Card.Bodydivcontour-card__bodydata-scope="card" data-part="body"
Card.Footerdivcontour-card__footerdata-scope="card" data-part="footer"
Card.Titleh3contour-card__titledata-scope="card" data-part="title"
Card.Descriptionpcontour-card__descriptiondata-scope="card" data-part="description"

The header and the body stack their content; the body takes the free height, so the footers of cards in one grid row line up. The footer lays out its items in a row.

Props

Card.Root:

PropValuesDefault
variant'outline', 'subtle', 'elevated''outline'
size'sm', 'md', 'lg': the padding and the gap between parts'md'
asChildboolean: render the only child, such as a link, as the cardfalse

Every part takes asChild and the props of its element, such as className, style and id; className is merged with the part's class.

Accessibility

  • Card.Root is a div without a role: it groups content visually. Card.Title is an h3; to fit the headings of your page, pass another level with asChild: <Card.Title asChild><h2>Essentials</h2></Card.Title>.
  • A link card is one link, named by its text: the title first, then the description. Keep the description short, and put no other links or buttons inside: interactive elements cannot nest.
  • A button may only contain phrasing content: in a button card, render the parts as spans with asChild.
  • Hover changes the border and the background, and keyboard focus shows a focus ring. The elevated shadow is decoration: a raised background and a border set the card apart without it.

Tokens

TokenUsed for
--ct-color-bg-surface, --ct-color-bg-raisedthe background: outline and subtle, elevated
--ct-color-border-defaultthe border: outline and elevated
--ct-shadow-raisedthe elevated shadow
--ct-color-bg-hover, --ct-color-border-stronga hovered link or button card
--ct-color-border-focus, --ct-border-width-focusthe focus ring
--ct-border-width-default, --ct-radius-panelthe border width and the corners
--ct-space-group-gap, --ct-space-panel-inset, --ct-space-section-gappadding and the gap between parts: sm, md, lg
--ct-space-control-gapbetween the title and the description
--ct-space-group-gapbetween the footer's items
--ct-text-heading, --ct-color-fg-defaultthe title
--ct-text-body, --ct-color-fg-mutedthe description; other content is fg-default
--ct-motion-feedbackthe hover transition