Skip to content

Card

A flexible content container with built-in support for the "stretched link" pattern — fully clickable cards that remain a single tab stop and produce valid HTML.

The Card component groups related information inside a clear visual boundary. When given an href, the entire card becomes a single, accessible click target — without nesting interactive elements or breaking keyboard navigation.

Accessibility Features

  • Single tab stop: A clickable card is one focusable element, not several. Keyboard users do not have to tab through a duplicated "title" link and "read more" link.
  • Valid HTML: No anchors are nested inside other anchors or buttons — the stretched overlay is a hidden span with aria-hidden="true".
  • Meaningful link text: The link wraps the card title, so screen readers announce the heading text — never a generic "read more".
  • Semantic container: Use the as prop to render the card as article, section, or li so the surrounding document outline stays correct.
  • Heading hierarchy: The title renders as an h3. Make sure that fits your page's heading structure (e.g. cards inside an h2 section).

A naive "clickable card" usually wraps the whole card in an <a> — which becomes invalid the moment you put a button or another link inside. The Card component instead links only the title, then stretches an absolutely-positioned overlay across the card so the entire surface is clickable:

<h3> <a href="/article"> <span aria-hidden="true" style="position:absolute; inset:0; z-index:1" /> Article title </a> </h3>

The result: one tab stop, accessible link text equal to the title, and full freedom to add other interactive elements inside the card — as long as you raise their z-index above the overlay (see Best Practices below).

Props Reference

interface CardProps { /** Card heading. Renders as an <h3>. */ title?: React.ReactNode; /** If provided, the title becomes a link that covers the entire card. */ href?: string; /** Card body content. */ children: React.ReactNode; /** Additional CSS class names. */ className?: string; /** HTML tag for the container. Default: "div". */ as?: 'div' | 'article' | 'section' | 'li'; }

Usage Examples

Static card

import { Card } from '@holmdigital/components'; <Card title="Project Overview"> This card contains important information about the current accessibility audit. </Card>

Clickable card (stretched link)

<Card title="WCAG 2.1 AA Compliance" href="/standards/wcag" > Read the latest guidance on conformance levels and success criteria. </Card>

Card grid as a semantic list

<ul style={{ display: 'grid', gap: '1rem', listStyle: 'none', padding: 0 }}> {articles.map(article => ( <Card key={article.id} as="li" title={article.title} href={article.url} > {article.summary} </Card> ))} </ul>

Rendering each card as <li> inside a <ul> lets screen readers announce "list, 8 items" — useful context for a card grid.

Best Practices

Nested interactive elements need a higher z-index. If you place a button or secondary link inside a card with href, the stretched overlay will block clicks. Either raise the inner element's z-index above the overlay, or remove the href and use explicit links/buttons instead.
  • Don't repeat the link target. A clickable card already exposes the title as the link — adding a separate "Read more" link doubles the tab stops without adding meaning.
  • Keep selectable text short. Long passages inside a clickable card make text selection awkward, since drags trigger navigation. Prefer summaries.
  • Match the heading level. The title is an h3. If your page already uses h3 for sub-sections, wrap the card grid in an h2 so document outline stays consistent.
  • Provide a visible focus style. Because focus moves to the title link (not the card), make sure your global focus ring is visible on links — this project uses a 3px black ring sitewide.