Skip to content

Checkbox

Edit on GitHub
@volvo-cars/css v2.3.0@volvo-cars/react-forms v2.0.0

Checkboxes let users toggle a binary choice or select multiple options from a list. Use Checkbox from @volvo-cars/react-forms, or build with plain HTML using native <input type="checkbox"> elements styled automatically by @volvo-cars/css.

import { Checkbox } from '@volvo-cars/react-forms';
export function CheckboxBasic() {
return (
<Checkbox name="updates" label="Send me product updates" defaultChecked />
);
}

The hint prop adds a description below the label, linked via aria-describedby. Use hints for supplementary context — place links here rather than in the label text.

Read the terms of service and privacy policy.

import { Checkbox } from '@volvo-cars/react-forms';
export function CheckboxWithHint() {
return (
<Checkbox
name="terms"
label="Accept terms of service"
hint={
<>
Read the <a href="#terms">terms of service</a> and{' '}
<a href="#privacy">privacy policy</a>.
</>
}
/>
);
}

Pass defaultChecked to pre-select a checkbox without managing state — see the opening example above.

Pass checked and onChange when you need to read or drive the state externally.

You can unsubscribe anytime.

import { Checkbox } from '@volvo-cars/react-forms';
import { useState } from 'react';
export function CheckboxControlled() {
const [checked, setChecked] = useState(false);
return (
<Checkbox
name="updates"
label="Send me product updates"
hint="You can unsubscribe anytime."
checked={checked}
onChange={(event) => setChecked(event.target.checked)}
/>
);
}

Set errorMessage to display an inline error and mark the checkbox invalid. The error is hidden when the checkbox is disabled. You can also force invalid styling with aria-invalid.

Must accept to proceed

import { Checkbox } from '@volvo-cars/react-forms';
export function CheckboxError() {
return (
<Checkbox
name="terms"
label="Accept terms of service"
errorMessage="Must accept to proceed"
/>
);
}

The indeterminate prop shows a visual “partially selected” state — useful for parent/child checkbox hierarchies. It is visual-only and does not affect the submitted value; you manage the checked state of each child yourself.

import { Checkbox } from '@volvo-cars/react-forms';
import { useState } from 'react';
export function CheckboxIndeterminate() {
const [news, setNews] = useState(true);
const [offers, setOffers] = useState(false);
const allChecked = news && offers;
const indeterminate = news !== offers;
return (
<div className="flex-col gap-16">
<Checkbox
name="subscriptions"
label="Subscriptions"
checked={allChecked}
indeterminate={indeterminate}
onChange={(e) => {
setNews(e.target.checked);
setOffers(e.target.checked);
}}
/>
<div className="flex-col gap-8 pl-24">
<Checkbox
name="news"
label="Product news"
checked={news}
onChange={(e) => setNews(e.target.checked)}
/>
<Checkbox
name="offers"
label="Special offers"
checked={offers}
onChange={(e) => setOffers(e.target.checked)}
/>
</div>
</div>
);
}

@volvo-cars/css styles native <input type="checkbox"> elements automatically — no class needed. If you add any class to the input, include the checkbox class to keep design system styles.

<div class="flex flex-col gap-16">
<div class="flex-row">
<input type="checkbox" name="updates" id="updates" value="updates" />
<label class="ml-8" for="updates">Send me product updates</label>
</div>
<div class="flex-row">
<input type="checkbox" name="terms" id="terms" value="terms" checked />
<label class="ml-8" for="terms">I agree to the terms</label>
</div>
<div class="flex-row">
<input type="checkbox" name="offers" id="offers" value="offers" disabled />
<label class="ml-8" for="offers">Get special offers</label>
</div>
</div>

Wrap the label and hint in a flex-col stack-4 container. Use micro text-secondary for hint text. Place links in the hint, not in the label.

Read the terms of service and privacy policy.

<div class="flex-row">
<input type="checkbox" name="terms" id="terms" value="terms" />
<div class="flex-col stack-4 ml-8">
<label for="terms">Accept terms of service</label>
<p class="micro text-secondary">
Read the <a href="#terms">terms of service</a> and
<a href="#privacy">privacy policy</a>.
</p>
</div>
</div>

Set aria-invalid="true" on the input and link an error message with aria-describedby.

Must accept to proceed

<div class="flex-row">
<input
type="checkbox"
name="terms"
id="terms-error"
value="terms"
aria-invalid="true"
aria-describedby="terms-error-msg"
/>
<div class="flex-col stack-4 ml-8">
<label for="terms-error">Accept terms of service</label>
<p id="terms-error-msg" class="micro text-feedback-red flex items-center">
Must accept to proceed
</p>
</div>
</div>

Key responsibilities from the WCAG audit:

  • Label every checkboxCheckbox requires a label prop. For CSS-only, pair each <input> with a <label for>.
  • Keep labels plain text — add any required links in the hint instead.
  • Error association — use errorMessage (React) or aria-invalid + aria-describedby (CSS-only) to communicate errors.
  • Pointer target size — the checkbox control is smaller than 44×44 px, but the adjacent label extends the clickable area.
  • Status messages — VoiceOver in Safari has known issues announcing error messages on checkboxes (WCAG 4.1.3). Test with multiple screen readers.
Prop Type Required Default
name string -
The name of the input to use when submitting the form.
label string -
Concise label for the checkbox. Labels should not contain HTML, add any required links in the `hint` instead.
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
required boolean - -
Makes the checkbox required.
errorMessage string - -
Set the error message of a checkbox and mark it invalid.
aria-invalid boolean - false
Force the checkbox to be invalid.
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.
indeterminate boolean - false
Indicates if the checkbox is in an indeterminate state, often used to show a "partially selected" state (e.g., when some child checkboxes are selected). This state is visual-only and does not affect the checkbox's submitted value.
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> - -
disabled boolean - false
Disables the input. Use sparingly as it can be non-obvious to users why an input has been disabled. Prefer showing validation messages and hints instead.
autoFocus boolean - false
Gives the input focus on page load. Use sparingly as it can be confusing to screen-reader and mobile users.
readOnly boolean - false
Makes the input 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 this input should be associated with. Defaults to the containing form element.
hint ReactNode - -
Additional hint or description.
enterKeyHint "done" | "go" | "next" | "previous" | "search" ... - -
Hint the browser about what label to show for the Enter button on mobile keyboards.
aria-describedby string - -
aria-labelledby string - -
onInvalid FormEventHandler<HTMLInputElement> - -
Fires if the input fails validation on form submit.
onFocus FocusEventHandler<HTMLInputElement> - -
onBlur FocusEventHandler<HTMLInputElement> - -
onKeyDown KeyboardEventHandler<HTMLInputElement> - -
onKeyUp KeyboardEventHandler<HTMLInputElement> - -