Cards

stable

Cards are entry points — they surface content and invite action. The base .card class sets background, border, and border-radius via --card-* Tier-3 tokens. Add variant modifiers, anatomy classes, and the interactive modifier as needed.

Variants

Apply .card first, then at most one variant modifier. All variants adapt to dark/light mode automatically via the semantic token layer.

Default

Background fill, no visible border. Use for most card contexts.

Outlined

Adds a --color-border edge. Use when the card sits on a same-level surface.

Highlight

High-contrast background. Adapts per theme — toggle the page theme to see.

VariantClassUse
Default .card Article lists, feature grids, project indexes. Background: --card-bg--color-bg-panel.
Outlined .card-outlined Adds var(--color-border) edge. Use when the surrounding surface matches the card's background and needs a visible separator.
Highlight .card-highlight Featured or promoted content. Light mode: --in0-void (darkest tone). Dark mode: --in1-iron + --in2-slate border.

Anatomy

When anatomy children are present, the wrapper's padding is removed automatically via :has() — each section carries its own var(--card-padding) (24px). A bare .card without anatomy children keeps its 24px padding.

Order matters for .card-media — it must be the first child so its top border-radius matches the card's corners. A divider appears between .card-body and .card-footer only.

Fern Valley Trail

3.2 km · Moderate

A shaded loop through old-growth forest with seasonal wildflower meadows along the ridge.

<div class="card">
  <!-- .card-media must be first child -->
  <div class="card-media">
    <img src="trail.jpg" alt="Fern Valley Trail">
  </div>
  <div class="card-header">
    <h3>Fern Valley Trail</h3>
    <p>3.2 km · Moderate</p>
  </div>
  <div class="card-body">
    <p>A shaded loop through old-growth forest…</p>
  </div>
  <div class="card-footer">
    <button class="btn btn-p btn-sm">View trail</button>
    <button class="btn btn-g btn-sm">Save</button>
  </div>
</div>

Anatomy reference

ClassPurposeNotes
.card-media Top image / visual area Must be first child. overflow:hidden clips image to card border-radius.
.card-header Title and metadata row Use semantic heading elements (h2h4) inside.
.card-body Main content area flex: 1 — expands to fill in equal-height grids.
.card-footer Actions row margin-top: auto pins to bottom. display: flex; flex-wrap: wrap lays out buttons in a row. Divider appears when preceded by .card-body.

Interactive cards

Add .card-interactive to any card that is a navigation target or action trigger. It adds hover, focus, and active states. Works on both <div> (with tabindex="0") and <a> elements.

Interactive (default)

Hover or focus me. Background shifts to --card-hover-bg.

Interactive + outlined

Variants compose with the interactive modifier.

Interactive + highlight

Hover shifts to --card-hover-bg from the highlight base.

<!-- div card (needs tabindex for keyboard access) -->
<div class="card card-interactive" tabindex="0"> … </div>

<!-- anchor card (whole card is the link) -->
<a href="/trail/fern-valley" class="card card-interactive"> … </a>

<!-- combined with anatomy -->
<a href="/article/01" class="card card-interactive">
  <div class="card-media"><img src="…" alt="…"></div>
  <div class="card-header"><h3>Article title</h3></div>
  <div class="card-body"><p>Summary text…</p></div>
</a>

Card grid

.card-grid creates a responsive auto-fit grid. Cards expand to fill columns and collapse to a single column on narrow viewports. Minimum column width: 280px.

Iron Night

Dark palette — deep, structural, typographic.

Birch Mist

Light palette — airy, open, editorial.

Forest

Accent palette — grounded, natural, purposeful.

<div class="card-grid">
  <div class="card card-interactive"> … </div>
  <div class="card card-interactive"> … </div>
  <div class="card card-interactive"> … </div>
</div>

Token reference

All --card-* tokens are defined in tokens/components.css. The highlight tokens are resolved by --color-card-highlight-* semantic tokens in tokens/dark-light.css.

TokenDefaultPurpose
--card-bgvar(--color-bg-panel)Default card background
--card-hover-bgvar(--color-bg-inset)Background on hover (interactive cards)
--card-bordertransparentBorder colour — transparent by default; .card-outlined overrides to --color-border
--card-radiusvar(--radius-lg) (8px)Border radius for card and media
--card-paddingvar(--space-md) (24px)Padding for bare cards and all anatomy sections
--card-highlight-bgLight: --in0-void · Dark: --in1-ironBackground for .card-highlight
--card-highlight-borderLight: transparent · Dark: --in2-slateBorder for .card-highlight

Overriding tokens

Override --card-* tokens on a scoped selector to retheme cards in one section without affecting the rest of the page.

/* Tighter padding for a dense list layout */
.article-list {
  --card-padding: var(--space-sm);
}

/* Rounded cards in a marketing section */
.feature-grid {
  --card-radius: var(--radius-xl);
}

/* Custom hover colour for a dark hero section */
.hero {
  --card-hover-bg: var(--color-bg-inset);
}

CSS reference

/* ── Card grid ── */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-md);
}

/* ── Card base ── */
.card {
  display: flex;
  flex-direction: column;
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  padding: var(--card-padding);
}
.card:has(> .card-header, > .card-body, > .card-footer, > .card-media) {
  padding: 0;
}

/* ── Variants ── */
.card-outlined  { border-color: var(--color-border); }
.card-highlight { background: var(--card-highlight-bg); border-color: var(--card-highlight-border); }

/* ── Interactive modifier ── */
.card-interactive {
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}
.card-interactive:hover         { background: var(--card-hover-bg); }
.card-interactive:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; }
.card-interactive:active        { transform: scale(0.99); }

/* ── Anatomy ── */
.card-media         { overflow: hidden; border-radius: var(--card-radius) var(--card-radius) 0 0; }
.card-media img     { width: 100%; }
.card-header        { padding: var(--card-padding); }
.card-body          { padding: var(--card-padding); flex: 1; }
.card-footer        { padding: var(--card-padding); margin-top: auto; display: flex; flex-wrap: wrap; gap: var(--space-xs); align-items: center; }
.card-body + .card-footer { border-top: 1px solid var(--color-border-subtle); }