Skip to content

Selectable Cards

Edit on GitHub

Cards with checkbox or radio inputs for selecting options with rich content.

@volvo-cars/css v2.5.1@volvo-cars/react-forms v2.0.1

Use CheckboxCard for independent multiple selection and RadioCard within a RadioGroup for a single selection.

Select trim level
550 000 kr

Base configuration with essential features

625 000 kr

Enhanced comfort and convenience features

750 000 kr

Premium features and advanced technology

Show codeHide 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>
);
}

Use CheckboxCard when users can select multiple options independently.

12 000 kr

3 years extended warranty coverage

Show codeHide 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"
/>
);
}

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.

Use RadioCard within a RadioGroup for single-selection scenarios. The RadioGroup provides the shared name attribute and manages selection state.

By default, cards manage their own state. Use defaultChecked on CheckboxCard or defaultValue on RadioGroup to set the initial selection.

For controlled behavior, use checked and onChange on CheckboxCard, or value and onChange on RadioGroup.

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.

For simple cases without interactive elements inside the card, use the .selectable-card CSS class with a <label> wrapper.

The card uses a slot-based layout:

  • slot="title" – Primary label
  • slot="meta" – Secondary information
  • slot="description" – Additional details
  • slot="image" – Optional image
volvocars.com
Show codeHide 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>

For radio buttons, give all inputs the same name attribute.

Show codeHide 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>

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.

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> (the RadioGroup component handles this)
  • Provide meaningful alt text for images, or use alt="" with aria-hidden="true" for decorative images

See the accessibility guidelines for detailed WCAG compliance information.

PropTypeRequiredDefault
namestring--
The name of the input to use when submitting the form.
valuestring-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
disabledboolean-false
Whether the input is disabled.
requiredboolean-false
Whether the input is required.
onInvalidFormEventHandler<HTMLInputElement>--
Fires if the input fails validation on form submit.
onFocusFocusEventHandler<HTMLInputElement>--
Focus event handler.
onBlurFocusEventHandler<HTMLInputElement>--
Blur event handler.
aria-labelstring--
ARIA label for the input.
aria-describedbystring--
ARIA describedby for the input.
aria-invalidboolean--
ARIA invalid state.
titlestring-
Main title or label for the card.
metastring--
Optional metadata (e.g., pricing, status).
descriptionstring--
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.
childrenReactNode--
Custom content that renders below the description. Use this for advanced layouts or interactive elements.
aria-labelledbystring--
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.
hiddenboolean--
idstring--
dirstring--
langstring--
slotstring--
translate"yes" | "no"--
classNamestring--
styleCSSProperties--
tabIndexnumber--
onPointerDownPointerEventHandler<Element>--
onPointerEnterPointerEventHandler<Element>--
onPointerLeavePointerEventHandler<Element>--
onPointerMovePointerEventHandler<Element>--
onPointerUpPointerEventHandler<Element>--
checkedboolean--
Whether the checkbox is checked. Makes the input controlled.
onChangeChangeEventHandler<HTMLInputElement, Element>--
Fires when the input is checked or unchecked.
defaultCheckedboolean--
Whether an (uncontrolled) checkbox is checked by default.
PropTypeRequiredDefault
valuestring-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
disabledboolean--
Whether the input is disabled.
onInvalidFormEventHandler<HTMLInputElement>--
Fires if the input fails validation on form submit.
onFocusFocusEventHandler<HTMLInputElement>--
Focus event handler.
onBlurFocusEventHandler<HTMLInputElement>--
Blur event handler.
aria-labelstring--
ARIA label for the input.
aria-describedbystring--
ARIA describedby for the input.
aria-invalidboolean--
ARIA invalid state.
titlestring-
Main title or label for the card.
metastring--
Optional metadata (e.g., pricing, status).
descriptionstring--
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.
childrenReactNode--
Custom content that renders below the description. Use this for advanced layouts or interactive elements.
aria-labelledbystring--
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.
hiddenboolean--
idstring--
dirstring--
langstring--
slotstring--
translate"yes" | "no"--
classNamestring--
styleCSSProperties--
tabIndexnumber--
onPointerDownPointerEventHandler<Element>--
onPointerEnterPointerEventHandler<Element>--
onPointerLeavePointerEventHandler<Element>--
onPointerMovePointerEventHandler<Element>--
onPointerUpPointerEventHandler<Element>--
PropTypeRequiredDefault
legendReactNode-
The legend to show above the radio group.
namestring-
The name of the radios to use when submitting the form.
hintReactNode--
Additional hint or description for the entire radio group.
childrenReactNode-
The radios.
requiredboolean--
Makes the radio group required.
onChangeChangeEventHandler<HTMLInputElement, Element>--
Fires when a radio is selected.
onInvalidFormEventHandler<HTMLFieldSetElement>--
Fires if the radio group fails validation on form submit.
disabledboolean-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.
readOnlyboolean-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.
formstring--
Id of a form element that the radios should be associated with. Defaults to the containing form element.
enterKeyHint"done" | "go" | "next" | "previous" | "search" ...--
errorMessagestring--
Set the error message of a radio group and mark it invalid.
aria-invalidboolean-false
Force the radio group to be invalid.
valuestring--
Select the radio with this value. Either `value` or `defaultValue` must be set. Makes the radio group controlled.
defaultValuestring--
Select the uncontrolled radio with this value by default. Either `defaultValue` or `value` must be set.
hiddenboolean--
idstring--
titlestring--
dirstring--
langstring--
slotstring--
translate"yes" | "no"--
classNamestring--
styleCSSProperties--
tabIndexnumber--
onPointerDownPointerEventHandler<Element>--
onPointerEnterPointerEventHandler<Element>--
onPointerLeavePointerEventHandler<Element>--
onPointerMovePointerEventHandler<Element>--
onPointerUpPointerEventHandler<Element>--