Photon Design System

Card

A bordered content panel — a header (title + description), a body, and an optional footer. Card is static chrome: a flat surface with a hairline border and no shadow (elevation is reserved for floating layers like menus and dialogs). It has no interactivity of its own.

For a label/control list, use BasicPage.Card; for a featured block on the page surface (an invitation form, a notice), use BasicPage.Callout.

Import

import { Card } from "@photon-ai/pho-ui/components/card";

Usage

The Root carries the vertical padding (pt-5 / pb-4) and the rhythm between parts (gap-4); Header, Content, and Footer carry the horizontal padding. A Stat or Stat.Group as the first or last child drops the matching inset so the tiles sit flush on the card edge; a chart as the first child drops the top inset the same way. The footer is unopinionated about justification — add justify-end for a dialog-style action bar.

Usage

Data since Aug 1.

Agent runs1,204 / 2,000
Storage3.1 GB / 10 GB
<Card.Root>
  <Card.Header>
    <Card.Title>Usage</Card.Title>
    <Card.Description>Data since Aug 1.</Card.Description>
  </Card.Header>
  <Card.Content>…</Card.Content>
  <Card.Footer className="justify-end">
    <Button size="sm" variant="outlined">
      Upgrade
    </Button>
  </Card.Footer>
</Card.Root>

Content only

Every part is optional — the Root alone is a valid surface.

A card can be just the surface — drop in any content.
<Card.Root>
  <Card.Content>A card can be just the surface.</Card.Content>
</Card.Root>

Full-bleed children

Because only the parts carry px-5, a direct child of the Root with no padding of its own — an image, a table, a Separator — runs edge-to-edge:

<Card.Root>
  <Card.Content>…</Card.Content>
  <Separator />
  <Card.Footer>…</Card.Footer>
</Card.Root>

Parts

All parts render plain elements and forward every native attribute.

  • Card.Root — <div> — the bordered surface; vertical padding and part rhythm live here
  • Card.Header — <div> — keeps the title/description pair tight
  • Card.Title — <h3> — the card heading
  • Card.Description — <p> — supporting copy in the secondary tone
  • Card.Content — <div> — the body; bring your own layout
  • Card.Footer — <div> — trailing row for actions or a status line

The CARD_SURFACE class string is also exported, for hosts that need the surface look on another element.

Accessibility

  • Semantics — a card is a generic <div> with no ARIA role. If it stands alone as a page landmark, pass role="region" and an aria-label (or point aria-labelledby at the title’s id); a card in a list usually needs neither.
  • Heading level — Card.Title renders an <h3> so it nests under a page h1 and section h2 without skipping levels. Keep the document outline in mind when placing cards.
  • Not interactive — the surface never takes focus. If the whole card should be clickable, put a link inside the title and stretch its hit area, rather than wrapping the card in a button.

Best practices

  • One subject per card. When a card starts growing sections, that’s a page layout, not a card.
  • Don’t nest cards. A bordered panel inside a bordered panel reads as noise — inside a card, separate with spacing or a Separator.
  • Cards sit on the page background; don’t stack them on another raised surface.
  • Keep the footer for trailing actions or status, not for more content.