Design system

Card

A white box with a grey border and 12px corners that groups related content. It lifts slightly on hover.

import { Card } from "@/ui";

Card is a plain <div> with a background, a border, rounded corners and a hover effect. It has no padding, header or footer: you add the layout inside it.

No screen imports Card yet. About 20 places in src/pages write the same rounded-xl border border-[var(--border-primary)] classes by hand. Use Card for new work so the style stays in one place.

Basic card

Give it padding and a width with className. p-4 (16px) is the usual padding.

<Card className="p-4 w-[300px]">
  <div className="text-[14px] font-medium">Pipeline by channel</div>
  <div className="text-[12px] text-[var(--color-text-secondary)] mt-1">
    Paid search $268K, LinkedIn $191K, Events $140K.
  </div>
</Card>
PropertyValue
Background--bg-tertiary (#FFFFFF)
Border1px --border-primary (#D4D9EA)
Radius12px (rounded-xl)
PaddingNone
HoverShadow --shadow-card (0 8px 24px, 15% grey) and a 2px lift
TransitionAll properties, 200ms

Card uses the older --bg-tertiary and --border-primary variables rather than --color-white and --color-grey-200. The values are the same today.

A row of metrics

Cards line up well as a row of KPIs. Hover over one to see the lift.

<Card className="p-4 w-[200px]">
  <div className="text-[12px] text-[var(--color-text-secondary)]">Sourced pipeline</div>
  <div className="text-[14px] font-semibold">$1.2M</div>
  <div className="text-[12px] text-[var(--color-green)]">+18% vs last quarter</div>
</Card>

A card with an action

Card is not a button or a link. When the card leads somewhere, put a real Button or link inside it, so it can be reached with the keyboard.

<Card className="p-4 w-[300px] flex flex-col gap-3">
  <div className="flex items-center justify-between gap-2">
    <span className="text-[14px] font-medium">Q3 webinar series</span>
    <Tag color="success-green">Live</Tag>
  </div>
  <div className="text-[12px] text-[var(--color-text-secondary)]">
    $412K pipeline from 96 leads. Paid search and LinkedIn.
  </div>
  <Button variant="blueGhost" size="sm" label="View campaign" icon={ArrowRight} iconPosition="suffix" onClick={openCampaign} />
</Card>

Turning off the hover

The hover lift is always on, which suggests the card can be clicked. For a card that does nothing on click, override it in className. Hover over the card below: it stays still.

<Card className="p-4 w-[200px] hover:shadow-none! hover:translate-y-0">
  …
</Card>

There is no prop for this. tailwind-merge replaces the lift (hover:translate-y-0 wins over hover:-translate-y-[2px]), but it does not see hover:shadow-none as clashing with hover:shadow-[var(--shadow-card)], so both stay and the card's shadow wins. The ! makes the override important. A static card is the more common case, so the hover default works against most uses.

Props

PropTypeDefaultNotes
childrenReactNode
classNamestringMerged with cn (clsx and tailwind-merge), so it can override padding, radius and hover.

Any other prop (onClick, role, aria-*, data-*) is passed to the <div>.

Accessibility

  • Card has no role. Screen readers read its contents in order, which is what you want for grouped content.
  • Do not put onClick on a Card on its own: a <div> cannot be focused or used with the keyboard. Put a Button or link inside instead.
  • When a list of cards is the main content of a page, give each card a heading so people can jump between them.

Do and don't

  • Do use Card to group one object or one metric: a campaign, an integration, a KPI.
  • Do keep padding consistent within a row of cards.
  • Don't nest cards inside cards. Use spacing or a divider inside one card.
  • Don't change the border colour or radius per screen. If a design needs a different card, that needs a design decision first.
  • Don't leave the hover lift on cards that do nothing when clicked.

On this page