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”- Card surface
- Vehicle image
- Brand label
- Model name
- Section heading
- Inner item
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”The Model Card maps to the WAI-ARIA radio group pattern. It has two semantic shapes — a compact card that is itself the choice, and an extended card whose inner items are the choices — so the accessibility responsibilities differ slightly between them.
For designers
Section titled “For designers”- The selected state relies on a non-text contrast shift on the card or item border. Keep that shift at or above 3:1 against the surface (WCAG SC 1.4.11).
- Make sure each card carries enough textual content (brand label, model name) for the radio role to be announced meaningfully — a vehicle image alone is not enough.
- Rested, hover, and active states must be distinguishable from one another without relying on colour alone.
For developers
Section titled “For developers”The Model Card supports two semantic shapes depending on whether the card itself is the choice or it holds inner choices.
Compact card (card is the choice)
Section titled “Compact card (card is the choice)”The card is the choice, but the role never sits on the .tng-model-card wrapper. Keep the wrapper a <div> and put role="radio" + aria-checked on its single .tng-model-card-trigger button. The wrapper stays a <div> so the same markup works whether or not the card later holds inner item buttons, and the selected / hover / focus styling is keyed off .tng-model-card-trigger (matched onto the wrapper via :has()) — never off the wrapper itself.
<div role="radiogroup" aria-label="Select a model"> <div class="tng-model-card"> <button type="button" class="tng-model-card-trigger" role="radio" aria-checked="true" > <!-- image + brand label + model name --> </button> </div> <div class="tng-model-card"> <button type="button" class="tng-model-card-trigger" role="radio" aria-checked="false" > <!-- … --> </button> </div></div>Consumer responsibilities:
- Manage roving tabindex — only the checked card’s trigger (or the first card’s trigger if none is checked) carries
tabindex="0"; the rest aretabindex="-1". - Handle arrow keys (
ArrowUp/ArrowDown/ArrowLeft/ArrowRight) to move focus between triggers and updatearia-checked.SpaceandEntershould also select the focused card. - Update
aria-checkedsynchronously on selection — the visual state is keyed off that attribute on the trigger, not a class.
Extended card (items are the choices)
Section titled “Extended card (items are the choices)”The wrapper stays a <div> — nesting <button> inside <button> is invalid HTML. The card header is a .tng-model-card-trigger button; express the “selected model” visual by setting aria-current="true" on that trigger (the wrapper picks it up via :has()), never on the wrapper itself. The header trigger also drives the disclosure (aria-expanded / aria-controls), and the inner items form their own role="radiogroup".
<div class="tng-model-card"> <button type="button" class="tng-model-card-trigger" aria-current="true" aria-expanded="true" aria-controls="prius-options" > <!-- image + brand label + model name --> </button> <div id="prius-options" role="radiogroup" aria-label="Fuel type"> <button type="button" role="radio" aria-checked="true" class="tng-model-card-item" > Electric </button> <button type="button" role="radio" aria-checked="false" class="tng-model-card-item" > Hybrid </button> </div></div>Roving-tabindex and arrow-key handling apply to the inner radiogroup the same way they do to a compact-card group.
Keyboard
Section titled “Keyboard”| Key | Compact card group | Inner item group |
|---|---|---|
Tab | Move focus into / out of the group | Same |
ArrowDown / ArrowRight | Move focus to next card and select it | Move focus to next item and select it |
ArrowUp / ArrowLeft | Move focus to previous card and select it | Move focus to previous item and select it |
Space / Enter | Select the focused card | Select the focused item |
Touch screen readers
Section titled “Touch screen readers”On mobile and tablet without an external keyboard, screen-reader users navigate the radiogroup with swipe gestures rather than focus-based keyboard navigation. Make sure each radio’s accessible name (brand label + model name) is meaningful out of visual context, since it is announced in isolation as the user swipes between options.
Visible focus is provided automatically by the global focus utility (:focus-visible outline ring). Do not suppress it.
Disabled
Section titled “Disabled”When a card or item is unavailable, set disabled on the <button>. The visual treatment falls back to rested and cursor: not-allowed. Disabled cards are skipped by the consumer’s roving-tabindex handler.