Model Card
A Model Card is a card surface for presenting a vehicle model with rested, hover, and selected states, plus an optional stack of inner item rows for related sub-choices like a powertrain selector.
Quarantine
A Model Card presents a single vehicle model on a card surface with rested, hover, and selected states. It has two layouts: a compact form (image, label, title) used in selection lists, and an extended form (compact content plus a stack of inner item rows) used when the model has been chosen and the user is picking a related sub-option such as a powertrain.
Anatomy
Section titled “Anatomy”- Model card
- Car
- Label
- Car name
When to use it
Section titled “When to use it”Use a Model Card when:
- The user is browsing or comparing vehicle models and the surface needs to read as a selectable tile.
- You need a uniform layout that holds a vehicle image, a brand label, a model name, and optionally a stack of related sub-choices.
- The picker is the primary action on the surface — the card itself, or one of its inner items, is interactive.
Avoid the Model Card when:
- You need a generic content container with no selection behaviour — use the Simple Card instead.
- The selection is a single binary choice — reach for a Radio or Chip.
- The card would only label content without enabling a selection — use a Tag or Badge.
Properties
Section titled “Properties”Layout
Two layouts share the same surface and token group. Compact carries the image, brand label, and model name only — used in selection lists where the whole card is the choice. Extended adds a section heading and a vertical stack of inner item rows — used once a model has been chosen and the user is picking a sub-option.
State
Rested, Hover, and Active are the three visual states, applied to both the card surface and each inner item. Active reads as the selected state — wired off aria-checked when the surface itself is the interactive, or aria-current when the surface is a container around inner interactives.
Inner item
A row primitive sized for a 20px icon plus a short label, with its own rested / hover / active states keyed off aria-checked. Items are grouped into their own role="radiogroup" inside the extended card; the card’s selected state and the item’s selected state move independently.
Mode
Toyota and Lexus brand modes resolve automatically through the active data-brand. The shared layout, padding, and structure stay identical between modes; tokens drive the brand-specific colour and stroke shifts (Lexus thickens the hover and active borders).
Platform considerations
Section titled “Platform considerations”Desktop
Model Cards sit comfortably in multi-column grids. Keep cards inside the same group aligned so the brand label, model name, and image baselines scan as a single row. Pointer hover gives the user a clear pre-selection cue before they commit to a card.
Tablet
The card hugs the content vertically and stretches to fill its column. Single-column layouts are common; let the surrounding grid decide width rather than forcing a fixed pixel size on the card.
Mobile
Cards span the full container width. Hover doesn’t apply on touch — make sure the rested → active state transition is clear so a tap visibly changes the surface. Keep the inner item list short enough that the extended card doesn’t grow past a screen height.
Best practices
Section titled “Best practices”Treat the Model Card as a picker tile — the surface is a container for a model, and the affordance to choose lives in the ARIA pattern, not in extra decoration.
Do
Wrap a set of cards in a role="radiogroup" with a descriptive aria-label, use the compact layout when the card itself is the choice and the extended layout when the card holds further sub-choices, and express the “selected model” visual on the extended card through aria-current="true" rather than nesting interactives.
Don't
Don’t nest a <button> inside a card that is already a <button> — switch to the extended layout’s <div> container instead. Don’t invent additional visual states beyond rested, hover, and active. Don’t override the active border colour; the contrast shift signals selection. Don’t use the card for non-selection content — use the Simple Card.
Content guidelines
Section titled “Content guidelines”Lead with the brand label, then the model name — the eye scans the brand first, then anchors on the model. Keep model names verbatim from the canonical product list so brand consistency holds across surfaces. The inner-item label is a short fuel-type or trim noun (“Electric”, “Hybrid”, “Plug-in”), never a full sentence; pair it with a 20px icon that reinforces the category. When the model is unavailable, disable the card rather than hiding it — the user still benefits from seeing the gap in the line-up.
Styles
Section titled “Styles”Elements
Section titled “Elements”<div class="tng-model-card"> <button class="tng-model-card-trigger"> <img alt="Toyota Prius" src="/_astro/prius.B6m1c0f0.png" /> <div> <span class="tng-label is-blue"> <i class="tng-icon icon-hybrid-electric-car-hev" aria-hidden="true" ></i> <span>Hybrid</span> </span> <div class="tng-text-body">Model Name</div> </div> </button></div>Card Item
Section titled “Card Item”<div class="tng-model-card"> <button class="tng-model-card-item"> <div class="tng-label is-blue"> <i class="tng-icon icon-plug" aria-hidden="true"></i> Electric </div> </button></div>Recipe
Section titled “Recipe”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.