Accessibility
A group of Model Cards maps to the WAI-ARIA Radio Group pattern — the group is a radiogroup and each card is a single-select radio — and meets WCAG 2.1 AA. An accessible Model Card group keeps the whole set reachable as one tab stop, lets arrow keys move the selection, keeps assistive technology informed of which card is checked, and gives every card an accessible name from its brand label and model name — so the choice is clear whether the user sees it, hears it, or tabs through it. The component has two layouts with slightly different responsibilities: a compact card that is itself the choice, and an extended card whose inner items are the choices and whose header carries the selected model via aria-current.
For designers
Section titled “For designers”- The card content — image, brand label, model name, and every state — must maintain accessible contrast on every supported surface. Source: WCAG 1.4.3 Contrast (Minimum).
- The selected state is signalled by a non-text contrast shift on the card or item border. Keep that shift at or above 3:1 against the surface, and don’t override the active border colour. Source: WCAG 1.4.11 Non-text Contrast.
- Rested, hover, and active states must be distinguishable from one another without relying on colour alone. Source: WCAG 1.4.1 Use of Color.
- Focus styles must stay visible, meet contrast requirements on every supported surface, and be clearly distinguishable from hover. Source: WCAG 2.4.7 Focus Visible.
- Give each card enough textual content (brand label, model name) for the radio to be announced meaningfully — a vehicle image alone is not enough of an accessible name.
- Use semantic colour tokens (e.g.
foreground/neutral/default) rather than fixed values, so the component adapts correctly across themes and both brand modes.
For developers
Section titled “For developers”A set of Model Cards is a WAI-ARIA radio group — the group is the radiogroup and each card is a radio — so the whole set is a single tab stop whose arrow keys move selection from card to card. Two layouts change where the radio sits: the compact card is itself the choice, while the extended card is a disclosure container whose inner item rows are the choices (the header opens them, the items are the radios). Either way the interactive semantics live on the <button>, never on the .tng-model-card <div> wrapper — the wrapper stays a plain container so the markup is valid whether or not it holds inner buttons, and the selected, hover, and focus visuals key off the button through :has(). The consumer owns the roving tabindex and arrow-key selection documented in the Keyboard interaction table; a card marked disabled is skipped in that rotation. Visible focus comes from the global :focus-visible ring — don’t suppress it. Each element’s exact role, accessible name, and state are in the Labelling elements section below.
Keyboard interaction
Section titled “Keyboard interaction”| Key | Compact card group | Inner item group |
|---|---|---|
| Tab / Shift + Tab | Move focus into / out of the group | Same |
| ↓ / → | Move focus to the next card and select it | Move to the next item and select it |
| ↑ / ← | Move to the previous card and select it | Move to the previous item and select it |
| Space / Enter | Select the focused card | Select the focused item |
Focus order
Section titled “Focus order”A radio group is a roving-tabindex composite, so the whole group is a single tab stop: only the entry card’s trigger carries tabindex="0" and the rest are tabindex="-1", while arrow keys move focus and selection within the group (that’s the Keyboard interaction table’s job, not tab order). Tabbing again leaves the group entirely.
- Trigger
- Radiogroup
- Card (radio)
Only the one real tab stop is pinned; the two roved siblings sit outside the tab order. Source: WCAG 2.4.3 Focus Order.
Labelling elements
Section titled “Labelling elements”Give every element the role, name, and state assistive technology needs.
Radiogroup
role="radiogroup" on the wrapper <div> around the cards — never on .tng-model-card itself. Name it with aria-label (e.g. "Select a model") or aria-labelledby, so it doesn’t announce as an unnamed group. In the extended card the inner item rows form their own nested role="radiogroup", named for the sub-choice (e.g. aria-labelledby pointing at a “Select fuel type” heading).
Card (radio)
In the compact layout the card is the choice: put role="radio" and aria-checked on its single .tng-model-card-trigger <button>, never on the .tng-model-card wrapper. Its accessible name comes from the brand label plus model name — keep that text meaningful, since a screen reader announces it in isolation as a touch user swipes through the group. In the extended layout the header trigger is instead a disclosure button (aria-expanded kept in sync with the open state, aria-controls pointing at the options region), and the choices are the inner .tng-model-card-item buttons, each role="radio" + aria-checked.
Selected state
aria-checked="true" marks the chosen radio — the compact card’s trigger, or the selected inner item — and updates synchronously on selection. On the extended card the “selected model” is a separate signal: set aria-current="true" on the header trigger (a container around interactives, not itself a radio). The visual is keyed off these attributes on the button, not off a class.
Disabled
When a card or item is unavailable, set the native disabled attribute on its <button> rather than hiding it — the user still benefits from seeing the gap in the line-up. The visual falls back to rested with cursor: not-allowed, and disabled cards are skipped by the roving-tabindex rotation.
Source: WCAG 4.1.2 Name, Role, Value.