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.
Description
What it does
Groups a self-contained unit of related content — a title, description, optional media, and optional actions — inside a bordered, elevated container. Cards are most often used as scannable entry points that link through to more detail, but can also be static summaries with no link at all.
Where it appears
Landing and index pages across Open Point and Social Point — the component library index, guidelines and patterns indexes, and foundations index on this site are all built from Card. In-product, cards summarise a consultation, a stakeholder record, or a community submission in a list or dashboard grid.
Why it exists
Government platforms surface a lot of distinct, browsable content — components, consultations, submissions, records. Card gives every one of those "pick one of these" surfaces the same bordered, scannable shape, so users learn the pattern once rather than parsing a new bespoke tile on every page.
Dependencies
Badge, Tag, Button, Containers
Anatomy
| Part | Required? | 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
The default appearance — a 1px border on a raised surface.
Outlined (default)
Neutral card with a visible border on a raised surface.
The default choice for nearly every card. Use for index and landing-page tiles, and any card that needs a clear visual boundary without extra visual weight.
A solid neutral fill with no border.
Filled
Card with a solid neutral fill and no border.
Use where a border would compete with a busy background, or to visually de-emphasise a card relative to outlined cards on the same page.
Combines a solid fill with a visible border.
Filled-outlined
Card combining a solid fill with a visible border.
Use when a card needs to read as clearly bounded but also sit slightly forward of a plain outlined card — for example a selected or highlighted item in a grid of otherwise outlined cards.
Brand-tinted surface for a single featured item.
Accent
Card using the brand accent colour for its surface.
Use sparingly, for a single card that should draw the eye above its neighbours — a featured or promoted item, never for routine list content.
No border or fill — spacing and structure only.
Plain
Card with no border and no background fill.
Use when the card only needs consistent internal spacing and slot structure, and the surrounding layout already establishes visual separation (e.g. cards inside a table cell or an already-bordered panel).
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
When to use
- Presenting one scannable item — a component doc, a guideline, a consultation, a stakeholder record — as part of a browsable grid or list.
- Summarising a self-contained unit of content that a user will act on or navigate from, such as "open this record" or "read this guide".
- Grouping media, a short description, and an action (e.g. a submission preview with a "Review" button) into one unit.
When not to use
- As a general-purpose layout wrapper for arbitrary page sections — use Containers instead, which doesn't imply a bounded, browsable "item".
- For a single piece of static informational text with no boundary purpose — a plain paragraph or a Container is lighter-weight.
- Nesting cards inside cards. If a card's content itself needs internal grouping, use plain containers or dividers inside it instead.
- As a substitute for a data table when the content is naturally tabular (many items with the same structured fields) — use Table instead.
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
| Part | Token | Value |
|---|---|---|
| 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
| Key | Action |
|---|---|
| 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 attribute | When to use | Example |
|---|---|---|
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.