Selectable Cards
Cards with checkbox or radio inputs for selecting options with rich content.
Use CheckboxCard for independent multiple selection and RadioCard within a RadioGroup for a single selection.
Show code
import { RadioCard, RadioGroup } from '@volvo-cars/react-forms';
export function RadioCardGroup() { return ( <RadioGroup name="trim" legend="Select trim level" defaultValue="core"> <RadioCard value="core" title="Core" meta="550 000 kr" description="Base configuration with essential features" /> <RadioCard value="plus" title="Plus" meta="625 000 kr" description="Enhanced comfort and convenience features" /> <RadioCard value="ultra" title="Ultra" meta="750 000 kr" description="Premium features and advanced technology" /> </RadioGroup> );}Checkbox card
Section titled “Checkbox card”Use CheckboxCard when users can select multiple options independently.
Show code
import { CheckboxCard } from '@volvo-cars/react-forms';
export function BasicCheckboxCard() { return ( <CheckboxCard name="warranty" title="Extended warranty" meta="12 000 kr" description="3 years extended warranty coverage" /> );}With image
Section titled “With image”Cards can include an image for visual identification. Pass an object with src and optional alt, loading, and aspectRatio properties.
Stack multiple checkbox cards using the stack-16 utility class.
Radio card
Section titled “Radio card”Use RadioCard within a RadioGroup for single-selection scenarios. The RadioGroup provides the shared name attribute and manages selection state.
Controlled and uncontrolled
Section titled “Controlled and uncontrolled”Uncontrolled
Section titled “Uncontrolled”By default, cards manage their own state. Use defaultChecked on CheckboxCard or defaultValue on RadioGroup to set the initial selection.
Controlled
Section titled “Controlled”For controlled behavior, use checked and onChange on CheckboxCard, or value and onChange on RadioGroup.
Interactive content
Section titled “Interactive content”When you need interactive elements (buttons, links) inside the card, use the React components. Interactive elements inside a CSS-only <label> wrapper are not accessible.
CSS-only usage
Section titled “CSS-only usage”For simple cases without interactive elements inside the card, use the .selectable-card CSS class with a <label> wrapper.
Content slots
Section titled “Content slots”The card uses a slot-based layout:
slot="title"– Primary labelslot="meta"– Secondary informationslot="description"– Additional detailsslot="image"– Optional image
Show code
<label class="selectable-card"> <input type="checkbox" /> <div slot="image" class="bg-feedback-gray flex items-center justify-center h-full" > <code>image</code> </div> <code slot="title">title</code> <code slot="meta">meta</code> <code slot="description">description</code></label>Radio group
Section titled “Radio group”For radio buttons, give all inputs the same name attribute.
Show code
<div class="stack-16"> <label class="selectable-card"> <input type="radio" name="trim" checked /> <span slot="title">Core</span> <span slot="meta">550 000 kr</span> <div slot="description"> <span>Base configuration with essential features</span> </div> </label>
<label class="selectable-card"> <input type="radio" name="trim" /> <span slot="title">Plus</span> <span slot="meta">625 000 kr</span> <div slot="description"> <span>Enhanced comfort and convenience features</span> </div> </label>
<label class="selectable-card"> <input type="radio" name="trim" /> <span slot="title">Ultra</span> <span slot="meta">750 000 kr</span> <div slot="description"> <span>Premium features and advanced technology</span> </div> </label></div>With image
Section titled “With image”Add an <img> with slot="image" for visual content. The image stacks above content at narrow widths. See Media for the .img base class and aspect-ratio utilities.
Accessibility
Section titled “Accessibility”Cards provide accessible labels through the semantic relationship between the <label> (or React component) and the input. The entire card is clickable, providing a large touch target.
Key considerations:
- Do not place interactive elements (links, buttons) inside CSS-only cards wrapped in
<label>– use the React components instead - For radio groups, wrap cards in a
<fieldset>with<legend>(theRadioGroupcomponent handles this) - Provide meaningful
alttext for images, or usealt=""witharia-hidden="true"for decorative images
See the accessibility guidelines for detailed WCAG compliance information.
CheckboxCard
Section titled “CheckboxCard”| Prop | Type | Required | Default |
|---|---|---|---|
name | string | - | - |
| The name of the input to use when submitting the form. | |||
value | string | - | on |
| The value of the checkbox that will be submitted with the form data. @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#value | |||
disabled | boolean | - | false |
| Whether the input is disabled. | |||
required | boolean | - | false |
| Whether the input is required. | |||
onInvalid | FormEventHandler<HTMLInputElement> | - | - |
| Fires if the input fails validation on form submit. | |||
onFocus | FocusEventHandler<HTMLInputElement> | - | - |
| Focus event handler. | |||
onBlur | FocusEventHandler<HTMLInputElement> | - | - |
| Blur event handler. | |||
aria-label | string | - | - |
| ARIA label for the input. | |||
aria-describedby | string | - | - |
| ARIA describedby for the input. | |||
aria-invalid | boolean | - | - |
| ARIA invalid state. | |||
title | string | ✓ | - |
| Main title or label for the card. | |||
meta | string | - | - |
| Optional metadata (e.g., pricing, status). | |||
description | string | - | - |
| Optional description text. | |||
image | { src: string; alt?: string; loading?: "lazy" |... | - | - |
| Optional image configuration. Can be either an object with image properties or a custom React element (e.g., Next.js Image). When using a custom element, ensure it respects the 52px height constraint applied by CSS. | |||
children | ReactNode | - | - |
| Custom content that renders below the description. Use this for advanced layouts or interactive elements. | |||
aria-labelledby | string | - | - |
| ARIA labelledby for the input. Only use this when you need to reference multiple elements for the label. The native <label> element already provides proper association. | |||
hidden | boolean | - | - |
id | string | - | - |
dir | string | - | - |
lang | string | - | - |
slot | string | - | - |
translate | "yes" | "no" | - | - |
className | string | - | - |
style | CSSProperties | - | - |
tabIndex | number | - | - |
onPointerDown | PointerEventHandler<Element> | - | - |
onPointerEnter | PointerEventHandler<Element> | - | - |
onPointerLeave | PointerEventHandler<Element> | - | - |
onPointerMove | PointerEventHandler<Element> | - | - |
onPointerUp | PointerEventHandler<Element> | - | - |
checked | boolean | - | - |
| Whether the checkbox is checked. Makes the input controlled. | |||
onChange | ChangeEventHandler<HTMLInputElement, Element> | - | - |
| Fires when the input is checked or unchecked. | |||
defaultChecked | boolean | - | - |
| Whether an (uncontrolled) checkbox is checked by default. | |||
RadioCard
Section titled “RadioCard”| Prop | Type | Required | Default |
|---|---|---|---|
value | string | - | on |
| The value that will be submitted with the form data if the radio is selected. @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio#data_representation_of_a_radio_group | |||
disabled | boolean | - | - |
| Whether the input is disabled. | |||
onInvalid | FormEventHandler<HTMLInputElement> | - | - |
| Fires if the input fails validation on form submit. | |||
onFocus | FocusEventHandler<HTMLInputElement> | - | - |
| Focus event handler. | |||
onBlur | FocusEventHandler<HTMLInputElement> | - | - |
| Blur event handler. | |||
aria-label | string | - | - |
| ARIA label for the input. | |||
aria-describedby | string | - | - |
| ARIA describedby for the input. | |||
aria-invalid | boolean | - | - |
| ARIA invalid state. | |||
title | string | ✓ | - |
| Main title or label for the card. | |||
meta | string | - | - |
| Optional metadata (e.g., pricing, status). | |||
description | string | - | - |
| Optional description text. | |||
image | { src: string; alt?: string; loading?: "lazy" |... | - | - |
| Optional image configuration. Can be either an object with image properties or a custom React element (e.g., Next.js Image). When using a custom element, ensure it respects the 52px height constraint applied by CSS. | |||
children | ReactNode | - | - |
| Custom content that renders below the description. Use this for advanced layouts or interactive elements. | |||
aria-labelledby | string | - | - |
| ARIA labelledby for the input. Only use this when you need to reference multiple elements for the label. The native <label> element already provides proper association. | |||
hidden | boolean | - | - |
id | string | - | - |
dir | string | - | - |
lang | string | - | - |
slot | string | - | - |
translate | "yes" | "no" | - | - |
className | string | - | - |
style | CSSProperties | - | - |
tabIndex | number | - | - |
onPointerDown | PointerEventHandler<Element> | - | - |
onPointerEnter | PointerEventHandler<Element> | - | - |
onPointerLeave | PointerEventHandler<Element> | - | - |
onPointerMove | PointerEventHandler<Element> | - | - |
onPointerUp | PointerEventHandler<Element> | - | - |
RadioGroup
Section titled “RadioGroup”| Prop | Type | Required | Default |
|---|---|---|---|
legend | ReactNode | ✓ | - |
| The legend to show above the radio group. | |||
name | string | ✓ | - |
| The name of the radios to use when submitting the form. | |||
hint | ReactNode | - | - |
| Additional hint or description for the entire radio group. | |||
children | ReactNode | ✓ | - |
| The radios. | |||
required | boolean | - | - |
| Makes the radio group required. | |||
onChange | ChangeEventHandler<HTMLInputElement, Element> | - | - |
| Fires when a radio is selected. | |||
onInvalid | FormEventHandler<HTMLFieldSetElement> | - | - |
| Fires if the radio group fails validation on form submit. | |||
disabled | boolean | - | false |
| Disable all radios in the group. Use sparingly as it can be non-obvious to users why an input has been disabled. Prefer showing validation messages and hints instead. | |||
readOnly | boolean | - | false |
| Makes the radios in the group read-only. Use sparingly, it's often preferred to present the data as regular text or in a table instead. | |||
form | string | - | - |
| Id of a form element that the radios should be associated with. Defaults to the containing form element. | |||
enterKeyHint | "done" | "go" | "next" | "previous" | "search" ... | - | - |
errorMessage | string | - | - |
| Set the error message of a radio group and mark it invalid. | |||
aria-invalid | boolean | - | false |
| Force the radio group to be invalid. | |||
value | string | - | - |
| Select the radio with this value. Either `value` or `defaultValue` must be set. Makes the radio group controlled. | |||
defaultValue | string | - | - |
| Select the uncontrolled radio with this value by default. Either `defaultValue` or `value` must be set. | |||
hidden | boolean | - | - |
id | string | - | - |
title | string | - | - |
dir | string | - | - |
lang | string | - | - |
slot | string | - | - |
translate | "yes" | "no" | - | - |
className | string | - | - |
style | CSSProperties | - | - |
tabIndex | number | - | - |
onPointerDown | PointerEventHandler<Element> | - | - |
onPointerEnter | PointerEventHandler<Element> | - | - |
onPointerLeave | PointerEventHandler<Element> | - | - |
onPointerMove | PointerEventHandler<Element> | - | - |
onPointerUp | PointerEventHandler<Element> | - | - |