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.
| Variant | Class | Use |
|---|---|---|
| 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
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
| Class | Purpose | Notes |
|---|---|---|
.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 (h2–h4) 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.
| Token | Default | Purpose |
|---|---|---|
--card-bg | var(--color-bg-panel) | Default card background |
--card-hover-bg | var(--color-bg-inset) | Background on hover (interactive cards) |
--card-border | transparent | Border colour — transparent by default; .card-outlined overrides to --color-border |
--card-radius | var(--radius-lg) (8px) | Border radius for card and media |
--card-padding | var(--space-md) (24px) | Padding for bare cards and all anatomy sections |
--card-highlight-bg | Light: --in0-void · Dark: --in1-iron | Background for .card-highlight |
--card-highlight-border | Light: transparent · Dark: --in2-slate | Border 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); }