Components
Checkbox
BetaCheckboxes let users select one or more options from a set, or toggle a single boolean setting on or off.
Purpose
Checkboxes are for binary selection — something is either included or it is not. In a travel booking context they appear wherever a guest must opt in to optional extras (travel insurance, room upgrades, airport transfers) or confirm a set of conditions (terms, consent, notification preferences).
Usage
Use a checkbox group when the user may select any number of items from a fixed set. Use a single standalone checkbox for a boolean toggle that is not time-sensitive — for example confirming agreement to terms. For a toggle that immediately performs an action without a form submit, prefer the Toggle component instead. Do not use checkboxes for mutually exclusive choices — when only one option may be active at a time, use RadioButton.
Checkboxes typically sit within a form step of the booking funnel (extras, consent), grouped under a <fieldset> with a shared legend.
Anatomy
Return transfers from your local airport.
- 1Control box — 16×16 px square with rounded-sm corners (4 px). Unchecked: white background, grey-300 border. Checked: teal background and border with white checkmark SVG overlay.
- 2Label — Sentence-case text to the right of the control. Clicking the label toggles the checkbox.
- 3Required marker — Red asterisk appended to the label when required is true. Marked aria-hidden; a visually-hidden "(required)" text is provided for screen readers.
- 4Description — Optional secondary text beneath the label. Gives context without cluttering the label itself.
- 5Help text — Guidance below the control pair. Hidden when an error is present.
- 6Error message — Validation text with warning icon, shown when the error prop is set. Turns the control border red.
- 7Focus ring — 2 px teal ring with 2 px offset, visible on keyboard focus only (focus-visible). Meets WCAG 2.4.7.
Variants
Unchecked
The default resting state. A grey-300 border on a white background signals that the option is available but not selected.
Checked
Teal background and border with a white checkmark. The selected state is visually distinct at a glance and never relies on colour alone — the checkmark icon provides a second signal.
Indeterminate
Shown when a parent checkbox controls a group of children and only some are checked. The white dash on a teal background is the conventional "mixed" signal. Set the indeterminate prop — it is applied via a ref on the underlying input element because the HTML indeterminate property is not an attribute.
When to use: Parent checkboxes in a hierarchical selection list. For example: "Select all destinations" controls a group of regional destination checkboxes where a subset is already chosen.
Use the indeterminate state on parent checkboxes when a sub-group is partially selected.
Indeterminate communicates partial selection at a glance before expanding the group.
Leave a parent checkbox in checked or unchecked state when some but not all children are selected.
Showing a fully unchecked or fully checked parent when the state is actually mixed misleads the user.
With description
A description prop adds secondary text directly beneath the label. Use it to clarify what the option includes without extending the label itself.
Checkbox group
Stack multiple checkboxes with consistent gap-3 spacing to form a selection group. Wrap the group in a <fieldset> with a <legend> to give assistive technology the shared context.
States
Focus
The focus ring appears only on keyboard focus (:focus-visible). It is a 2 px teal ring with 2 px offset around the control box. Mouse clicks do not show the ring.
Disabled
The entire control — box, label, and description — is rendered at reduced opacity. The cursor changes to not-allowed. The input cannot be toggled and is removed from the tab order.
Error
An error message is shown below the control when validation fails. The control border turns red. Use this when a required checkbox has not been checked before form submission.
You must accept the terms to complete your booking.
With help text
Help text appears below the control and provides context or guidance. It is hidden when an error message is present.
We will email you personalised deals based on your destinations.
Best practices
Wrap checkbox groups in a fieldset with a descriptive legend so screen readers can announce the group context.
A fieldset with legend gives the group a shared accessible name.
Render a list of checkboxes with no wrapping fieldset or group label.
Without a group label, each checkbox is announced in isolation with no shared context.
Write labels as short, specific noun phrases: "Travel insurance", "Airport transfers", "Room upgrade".
Clear, specific labels like these make the choice unambiguous before the tap.
Use generic labels like "Option 1" or "Extra" that do not name the specific choice.
Vague labels force the user to seek context elsewhere before deciding.
Write error messages that explain what is required: "You must accept the terms to complete your booking."
Error messages guide the user to the exact action needed to proceed.
Use a generic error like "Required" without explaining what the user needs to do.
'Required' alone does not tell the user what to do or why.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label* | string | — | Visible label text rendered beside the control. Clicking the label toggles the checkbox. |
| id | string | — | HTML id for the input element. Auto-generated when omitted. Used to associate the label via htmlFor. |
| description | string | — | Secondary text rendered directly below the label. Use for clarifying copy that does not belong in the label. |
| checked | boolean | — | Controlled checked state. When provided, the component is fully controlled and requires an onChange handler. |
| defaultChecked | boolean | false | Uncontrolled initial checked state. Use when you do not need to track the value in React state. |
| indeterminate | boolean | false | Puts the control into the mixed/partial-check state. Applied via the indeterminate DOM property on the input. Use for parent checkboxes in hierarchical groups. |
| disabled | boolean | false | Disables interaction. Applies reduced opacity and not-allowed cursor. Removes the input from the tab order. |
| required | boolean | false | Marks the field as required. Adds a red asterisk after the label (visual) and a visually-hidden "(required)" text for screen readers. |
| error | string | — | Validation error message. Turns the control border red and renders the message below with a warning icon. |
| helpText | string | — | Guidance text shown below the control. Hidden when error is set. |
| onChange | (event: ChangeEvent) => void | — | Change handler. Required when using controlled mode (checked prop). Receives the native change event. |
| className | string | — | Additional Tailwind classes merged onto the root wrapper element. |
Accessibility
Keyboard interaction
| Key | Action |
|---|---|
Tab | Move focus to the next interactive element |
Shift + Tab | Move focus to the previous interactive element |
Space | Toggle the checkbox on or off |
Label association
The label prop is connected to the input via htmlFor / id. Clicking anywhere on the label text or description toggles the checkbox — important for touch targets and motor accessibility.
Groups with fieldset and legend
When rendering multiple checkboxes together, always wrap them in a <fieldset> and provide a <legend>. Screen readers announce the legend text before each checkbox label, giving users the shared group context without it being embedded in every label individually.
<fieldset className="border-0 p-0 m-0">
<legend className="text-sm font-semibold text-kuoni-dark mb-3">Holiday extras</legend>
<div className="flex flex-col gap-3">
<Checkbox label="Travel insurance" />
<Checkbox label="Airport transfers" />
</div>
</fieldset>
Indeterminate state
The indeterminate DOM property is not reflected as an HTML attribute, so it cannot be set in JSX. The Checkbox component handles this internally via a useEffect and a ref. Screen readers announce indeterminate checkboxes as "mixed" — the ARIA-defined value for partially-selected state.
Error announcement
When error is set:
aria-invalid="true"is applied to the input element.aria-describedbypoints to the error paragraph.- The error paragraph has
role="alert"so assistive technology announces the message immediately when it appears.
Colour and contrast
The checked state uses a teal background (#005B55) with a white checkmark. Contrast between the icon and background is above 7:1. The error border colour (#C0392B) is accompanied by both a warning icon and error text — never colour alone.
| Element | Foreground | Background | Ratio | WCAG |
|---|---|---|---|---|
| Checkmark icon | White #FFFFFF | Teal #005B55 | 7.0:1 | AAA |
| Label text | #1A1A1A | White #FFFFFF | 17.4:1 | AAA |
| Error text | #C0392B | White #FFFFFF | 5.4:1 | AA |
Related components
- RadioButton — use instead when only one option in a set may be selected.
- Toggle — use instead for a boolean setting that applies immediately without a form submit.