On this page
Checkbox
Let users select one or more options from a group, or toggle a single option on or off.
- Overview
- Rules and guidance
- Properties
- Styles
- Accessibility
When to use
- When users can select any number of options from a list, including none or all
- When users need to opt in or confirm something before proceeding, such as agreeing to terms
- When a single option needs to be explicitly turned on or off
When not to use
- When only one option can be selected at a time - use Radio instead. Checkboxes imply that multiple selections are valid.
- When the list is very long - use Select Field or Combobox Field instead. A long checkbox list is difficult to scan.
- When selection should take immediate effect - a checkbox is a form input; its value is submitted. If toggling should trigger an immediate action, use a different interaction pattern.
- To show a pre-selected option the user cannot change - the disabled state is not supported. Display the value as plain text or use a read-only Summary List instead. Do not use the disabled prop.
Anatomy
- Group label
- Group hint text
- Checkbox control
- Item label
- Item hint text
- Error text
Basic usage
Use CheckboxField to group related checkboxes. The label prop becomes the group's legend, which screen readers announce before each individual checkbox.
Edit live code
<CheckboxField label="How can I reduce my carbon footprint?" optional> <Checkbox label="Go plant based" id="plant-based" /> <Checkbox label="Take fewer flights" id="flights" /> </CheckboxField>
Variants and features
With hint text
Hint text can be added at the group level (applies to the whole group) or at the item level (applies to one option). Use group hint text to clarify what the question is asking. Use item hint text when a specific option needs extra explanation.
Edit live code
<CheckboxField label="How can I reduce my carbon footprint?" hint="Select all the changes you are willing to make" > <Checkbox label="Go plant based" id="plant-based-hint" /> <Checkbox label="Take fewer flights" id="flights-hint" hint="One long-haul return flight can outweigh a year of other savings" /> </CheckboxField>
Standalone checkbox
A single Checkbox without a CheckboxField wrapper is appropriate when the intent is self-evident and no group label is needed, such as accepting terms and conditions.
Edit live code
<Checkbox id="info" label="Tell me more about reducing my carbon footprint" />
A standalone checkbox can carry its own error, for example when it must be ticked to continue:
Edit live code
<Checkbox id="info-error" error="You must agree in order to receive more information" label="Tell me more about reducing my carbon footprint" />
Optional groups
Mark the group as optional with the optional prop on CheckboxField. This appends "(optional)" to the group label. Individual items can also be marked optional with the optional prop on Checkbox, though this is uncommon.
Edit live code
<CheckboxField label="How can I reduce my carbon footprint?" optional> <Checkbox label="Go plant based" id="plant-based-optional" /> <Checkbox label="Take fewer flights" id="flights-optional" /> </CheckboxField>
Rich labels
A label accepts any React content, not just a string. Use this to include a Text Link in the label, for example when linking to terms the user is agreeing to.
Edit live code
<Checkbox id="terms" label={ <> Tell me more about reducing my{' '} <TextLink href="https://friendsoftheearth.uk" opensInNewWindow> carbon footprint </TextLink> </> } />
Common form features
For validation, error messages, and integration with the Form component, see the Forms pattern guide.
usage
- Checkbox must not be used when only one option from a group may be selected at a timeCheckboxes imply that multiple values are valid. Using them for single-select misrepresents the input's behaviour and confuses users who expect to be able to select more than one.Copy rule ID for checkbox.not-for-single-select
- Checkbox must not be passed the disabled propThe prop passes through to the native input with no Nebula styling and removes the field from the tab order, making it invisible to keyboard users with no accessible indication of why.Copy rule ID for checkbox.no-disabled
- Checkbox should be replaced with plain text. For example in a SummaryList. When displaying a value the user cannot editA field that looks interactive but cannot be changed is confusing. Plain text communicates the value without implying editability.Copy rule ID for checkbox.non-editable-as-text
- Checkbox should not be pre-selected by default unless the user has previously chosen the valuePre-selecting options increases the chance that users will overlook a question and submit an unintended answer. GDS research found that pre-selection reliably causes errors on government forms. Restore a prior selection only when the value was explicitly chosen by this user.Copy rule ID for checkbox.no-preselection
content
- Checkbox item label should be written in sentence caseSentence case is the standard for all Nebula labels and is easier to scan than title case or all-caps.Copy rule ID for checkbox.label-sentence-case
- Checkbox item label should use positive framing where possibleNegative framing (e.g. "Do not send me emails") requires users to double-negate to understand the checked state. Positive framing ("Send me email updates") is clearer for all users.Copy rule ID for checkbox.label-positive-framing
- Checkbox label should use first-person phrasing when the checkbox confirms the user's agreement to something'I agree to the terms and conditions' is clearer and more natural than 'You agree to the terms and conditions' for a control the user is operating on their own behalf.Copy rule ID for checkbox.terms-first-person
- Checkbox group should list options in alphabetical order unless a different order more closely matches users' expectationsAlphabetical order is the default because it has no implied hierarchy and is easy to scan. Frequency-based ordering may be appropriate when one option is selected by the large majority of users.Copy rule ID for checkbox.alphabetical-order
- Checkbox item label should not end with a comma, semicolon, or full stopTrailing punctuation after checkbox labels adds visual noise. Labels are not sentences in this context.Copy rule ID for checkbox.label-no-trailing-punctuation
validation
- CheckboxField error text should follow the pattern "Select [at least one / the] [noun]" when no option has been selectedThis pattern is direct and tells the user exactly what action to take. Vague messages like "This field is required" do not describe what to select.Copy rule ID for checkbox.group-error-message-pattern
- Checkbox error text should not be shown at both the group level (CheckboxField) and the individual item level simultaneouslyShowing the same error in two places adds noise and can imply separate problems. Use CheckboxField's error prop to surface a single group-level error, or per-item errors for item-specific failures, but not both at once.Copy rule ID for checkbox.group-error-not-duplicated
interaction
- Checkbox will set aria-invalid on the native input automatically when an error is presentCopy rule ID for checkbox.will-aria-invalid
composition
- Checkbox will build aria-describedby automatically, linking the input to its error element and hint text by generated idCopy rule ID for checkbox.will-aria-describedby
- CheckboxField will set aria-required on each child Checkbox automatically, based on the optional propCopy rule ID for checkbox.will-checkboxfield-aria-required
- Checkbox group must be wrapped in a CheckboxField when two or more Checkbox components share a common purposeCheckboxField renders a fieldset and legend, which is the accessible HTML pattern for grouping form controls. Without it, screen readers cannot associate individual checkboxes with their group label.Copy rule ID for checkbox.group-requires-checkboxfield
- Checkbox must have a unique id propThe id prop links the native input to its label element via htmlFor. Without it, the label is not programmatically associated with the input, making the checkbox inaccessible to screen readers.Copy rule ID for checkbox.id-required
Checkbox
Also accepts all props from HTMLAttributes, InputHTMLAttributes.
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
id | string | — | Yes | Unique `id` for the input. Links the label, hint and error text to it. |
aria-describedby | string | — | No | Id(s) of extra elements that describe the input. When set, this replaces the link to the `hint`; the error is always linked. |
error | ReactNode | — | No | Error message shown below the checkbox. Marks the input as invalid. Falls back to the error for this `name` from the surrounding `Form`. |
label | ReactNode | — | Yes | Text shown next to the checkbox. |
hint | ReactNode | — | No | Helper text shown below the label and linked to the input via `aria-describedby`. |
data-group | boolean | — | No | Set automatically by `CheckboxField` to style the label as part of a group. You don't normally need to set it. |
optional | boolean | — | No | Appends "(optional)" to the label. |
CheckboxField
Also accepts all props from FieldsetHTMLAttributes, HTMLAttributes.
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
label | string | — | Yes | Legend text that labels the group of checkboxes. |
hint | ReactNode | — | No | Helper text shown under the label. |
error | ReactNode | — | No | Error message shown below the group. Marks the group as invalid (`aria-invalid` on the fieldset), gives its checkboxes the error styling and links the error to them with `aria-describedby`. Falls back to the error for this `name` from the surrounding `Form`. |
optional | boolean | false | No | Shows the field as optional rather than required, and stops its checkboxes being marked `aria-required`. |
Styles
checkbox-item
display: flex;
flex-wrap: wrap;
position: relative;
padding: ${space[1]} 0;
checkbox-input
The native checkbox, visually hidden but still the control that is focused and announced.
opacity: 0;
width: ${space[5]};
height: ${space[5]};
margin: 0;
padding: 0;
z-index: 1;
cursor: pointer;
hint-text
display: block;
width: 100%;
padding-left: ${space[7]};
checkbox-label
The label text, which also draws the visible box and tick with its pseudo-elements.
padding-left: ${space[2]};
align-self: center;
cursor: pointer;
&:before,
&:after {
content: '';
position: absolute;
}
&:before {
top: ${(space[7] - space[5]) / 2};
left: 0;
width: ${space[5]};
height: ${space[5]};
border: ${borderWidths.standard} solid ${colors.border};
background: ${colors.canvas};
border-radius: ${radii.standard};
}
&:after {
background: url("data:image/svg+xml,…");
top: ${(space[7] - space[5]) / 2 + (space[5] - space[4]) / 2};
left: ${(space[5] - space[4]) / 2};
width: ${space[4]};
height: ${space[4]};
opacity: 0;
}
input:checked + &:before {
background: ${colors.canvas};
border-color: ${colors.border};
}
input:checked + &:after {
opacity: 1;
}
input:focus-visible + &:before {
${focusRing}
}
input[data-error] + &:before,
input[aria-invalid] + &:before {
border-width: ${borderWidths.heavy};
border-color: ${colors.error};
}
In brief
Checkbox is a native checkbox input with a visible label. A screen reader announces the label, the role checkbox and whether it is checked, and CheckboxField adds a group label.
What Nebula provides
- A native input of type checkbox, hidden by making it transparent, so it is still the control that takes focus and is announced. Space toggles it.
- The label is linked to the input, so clicking it toggles the checkbox. A hint is linked as a description, not as part of the name.
- An error marks the input invalid and is linked to it as a description. It shows an icon and text, not colour alone.
- CheckboxField wraps the group in a fieldset with a legend that carries the label and hint.
Keyboard behaviour
Each checkbox is a tab stop, toggled with Space.
Interactions
- Tab - Moves focus to the next checkbox
- Space - Toggles the focused checkbox
Accessible design
Give every group of related checkboxes a label
The label is read before each checkbox in the group, so "Telephone" is heard as an answer to "How may we contact you?". Use CheckboxField and write a label that says what the group is for.
WCAG: 1.3.1 Info and Relationships 3.3.2 Labels or Instructions
Say what is required in the group hint, not only in the error
If a user must tick at least one, tell them before they submit, for example "Pick at least one". An error shown only after submitting is too late for the first attempt.
WCAG: 3.3.2 Labels or Instructions
Accessible development
Always pass a unique id to each Checkbox
The id is what links the label, hint and error to the input. Without a unique one the checkbox has no name, or takes the name of another.
WCAG: 1.3.1 Info and Relationships 4.1.2 Name, Role, Value
If a group needs an error, pass it to CheckboxField
The field marks the group invalid and links its error to every checkbox, so it is read once per checkbox. Pass error to a single Checkbox only when it stands alone.
WCAG: 3.3.1 Error Identification
If ticking nothing is a valid answer, set optional on CheckboxField
Without it every checkbox in the group is marked as required to a screen reader, and the label does not say the group is optional.
WCAG: 3.3.2 Labels or Instructions
Accessibility testing status
Reviewed on 9 October 2026 against v17.0.1.
| Test | Status | Tested with |
|---|---|---|
| Default state | Tested | axe-core via the Storybook test runner (CI) |
| Advanced states | Partially tested | axe-core via the Storybook test runner (CI) |
| Keyboard navigation | Manually tested | keyboard (reported by the owner) |
| Screen reader | Partially tested | VoiceOver with Chrome (reported by the owner) |
What these tests and statuses mean
The table records which checks were run on this component on its own. It is not a statement of WCAG conformance, and it does not cover how the component is used.
The tests
- Default state: The component as it first appears, before anything is clicked or changed.
- Advanced states: Every other state and variant: error, open, selected, each size and style, and hovering.
- Keyboard navigation: Whether every function can be reached and used with the keyboard alone, with a visible focus ring.
- Screen reader: Whether a screen reader announces what is on screen correctly: the name, the role and any state.
The statuses
- Tested: Every check in this row passed in an automated run that repeats on every pull request.
- Manually tested: Every check in this row was run once, by a person or with a tool they started, and all of them passed. It is not re-run on every change.
- Partially tested: Some checks in this row were run once and passed; the rest are still to do.
- Issue found: A check in this row failed. The ticket is shown beside the status.
- Not tested: No check in this row has been run yet.
- Not applicable: This row does not apply to the component, for the reason given beside it.