Skip to main content

Card

A bordered container that groups related content, media, and actions into a self-contained unit. Use to present a scannable summary that links through to more detail. Don't use as a general-purpose layout wrapper — see Containers instead.

OverviewStyleAccessibility

Description

Anatomy

PartRequired?Notes
Container Required The bordered root element. Sets background, border, radius, and internal spacing via the --spacing custom property (defaults to --wa-space-l).
Media Optional An image or video slotted at the start of the card, stretching to fill the card's width. Use --wa-frame:landscape or a fixed aspect ratio wrapper to keep media consistent across a grid.
Header Optional A title or label region above the body. Can include header actions (e.g. an overflow menu) via the header-actions slot.
Body Required The card's default (unnamed) slot — title, description, and any other primary content.
Footer Optional A bottom row for actions, metadata, or a call to action, visually separated from the body. Supports a footer-actions slot for buttons aligned to the end.

Variants

States

State Behaviour
Default Card is static and fully visible. If the card (or its title) is a link, it shows the default link/pointer affordance on the whole card, not just the title text.
Hover When a card is a link, the border colour shifts to the accent token and the card lifts slightly (translateY). Any interactive footer content (e.g. a button) shows its own hover state independently.
Focus When a card is a link, the focus ring appears around the entire card, not a partial region — implemented as a single stretched anchor covering the card rather than making the whole non-link surface focusable.
Loading Replace body content with a wa-spinner or skeleton while data loads. Keep the card's border and dimensions stable so the surrounding grid doesn't reflow.

Usage guidelines

Do / Don't

Do

Make the whole card a single link when the card links anywhere, so the click target isn't limited to the title text.

Don't

Don't put multiple competing links inside a card's body — a card should have one primary destination.

Do

Keep card titles short and scannable; put the detail in the description.

Don't

Don't wrap headline-length text across four or five lines in a card title — shorten it or move detail to the description.

Do

Use appearance consistently within one grid of cards — pick outlined or filled and stay with it.

Don't

Don't mix appearances arbitrarily within the same grid; reserve a second appearance (e.g. accent) for a genuinely singled-out item.

Do

Keep footer actions to one primary action plus at most one secondary action.

Don't

Don't stack more than two actions in a card footer — move additional actions to the card's own detail page.

Layout & Spacing

Card internal spacing (--spacing): defaults to --wa-space-l, applied around and between the media, header, body, and footer sections. Gap between cards in a grid: --op-space-24 (24px) is the convention used across Orbit's own index pages. Grid columns: repeat(auto-fill, minmax(260px–300px, 1fr)) is the common pattern for card indexes on this site.

Tokens

PartTokenValue
Card surface (outlined, default) --wa-color-surface-default (scoped to --wa-color-surface-raised in Orbit) Orbit overrides this per-component so outlined cards sit on the raised surface rather than the shared default-surface token every other component also reads.
Card surface (filled) --orbit-color-surface-default Set directly rather than reusing --wa-color-neutral-fill-quiet, so nested content (e.g. a badge) that also reads that shared token doesn't inherit the card's fill.
Card border --wa-color-surface-border 1px border on outlined and filled-outlined appearances.
Border radius --wa-panel-border-radius Matches Orbit's shared panel radius scale used by other bordered containers.
Title text --op-color-text-primary Card titles use the same primary text token as body copy elsewhere in Orbit.
Description text --op-color-text-secondary Slightly muted so the title remains the clear visual anchor.
Focus ring --op-color-interactive-focus Applied to the card's stretched link, not the card container itself.

Engineering notes

  • Use the wa-card element. Set the appearance attribute (outlined | filled | filled-outlined | accent | plain) — do not reimplement card styling with plain divs.
  • When the whole card should be a single link, don't wrap wa-card itself in an anchor. Instead render one anchor inside the card, absolutely positioned to cover the card's full area (a "stretched link"), and give it the card's accessible name via visually-hidden text. This keeps the card's own slots (media, footer) free to hold non-link content like status badges.
  • For SSR, set with-header, with-media, and/or with-footer attributes when slotting content into those regions — without them, only the default-slot body renders before hydration.
  • Don't nest a second interactive stretched-link inside a card footer — footer content should be genuinely separate actions (e.g. a wa-button), not another full-card link.
  • Orbit's own CardTile component (src/components/CardTile.astro) wraps wa-card with this stretched-link pattern plus an optional thumbnail/eyebrow/footer slot structure — reuse it for any new index-style card grid on this site rather than hand-rolling another bespoke card class.

Keyboard interaction

KeyAction
Tab Moves focus to the card's link (if any) as a single stop, then to any interactive footer content (e.g. a button) in DOM order.
Enter Activates the focused card link or footer button.
Shift+Tab Moves focus to the previous interactive element before the card.

Why it matters

Government users browsing a grid of consultations, records, or components rely on a predictable, single tab stop per card. A card whose only click target is a small title link — while the whole visual card looks clickable — creates a mismatch between what sighted mouse users and keyboard/screen reader users can actually activate.

Focus

Focus lands on the card's stretched link (or its title if there is no separate link), never on the wa-card element itself. The focus ring should visually outline the entire card, not just the title text, so the affordance matches what a mouse user perceives as clickable. Footer or header-actions content (e.g. a button) keeps its own independent focus stop, in DOM order after or before the card's main link depending on markup order.

ARIA

Role or attributeWhen to useExample
Accessible name via visually-hidden text When a card's clickable target is a stretched link rather than a visible title link, give that anchor an accessible name matching the card's title so screen readers announce the destination, not "link". <a href="/consultations/123" class="card-link"><span class="visually-hidden">Draft transport strategy</span></a>
aria-busy Set to true on the card while its content is loading asynchronously; remove once loaded. <wa-card aria-busy="true"><wa-spinner label="Loading consultation"></wa-spinner></wa-card>

Contrast

Title text (--op-color-text-primary) against the card surface: must meet 4.5:1. Description text (--op-color-text-secondary) against the card surface: must meet 4.5:1 at --op-text-sm. Card border (--wa-color-surface-border) against the surrounding page background: recommended to meet 3:1 so the card boundary remains perceivable, though WCAG 1.4.11 does not strictly require this for a purely decorative border. Accent appearance: verify title and description text still meet 4.5:1 against the accent-tinted surface — don't assume a brand tint automatically passes contrast.

Touch targets

The card's stretched link should cover the card's full visual area, giving it a touch target far larger than the 44x44px minimum. Any separate interactive element in the footer or header-actions slot (e.g. a button) must independently meet the 44x44px minimum.

Things to avoid

  • Do not make only a small title link inside a large visual card — see callout_watch above.
  • Do not place two competing links or a link plus a full-card stretched link in the same card — a screen reader user will not be able to tell which one activates on Enter from a shared focus stop.
  • Do not rely on the hover lift/border-colour change alone to indicate a card is interactive — keyboard and touch users need the focus ring and cursor affordance, not a hover-only cue.
  • Do not remove the card's border or background entirely on the plain appearance unless the surrounding layout already provides a clear boundary — an unbounded plain card can be mistaken for unstructured page content.

Was this page helpful?

Updated 5 October 2026