# Checkbox Let users select one or more options from a group, or toggle a single option on or off. Section: components Docs: https://nebula.kaluza.com/components/checkbox --- ## 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](/components/radio) instead. Checkboxes imply that multiple selections are valid. - **When the list is very long** - use [Select Field](/components/select-field) or [Combobox Field](/components/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](/components/summary-list) instead. Do not use the disabled prop. ## Anatomy 1. **Group label** 2. **Group hint text** 3. **Checkbox control** 4. **Item label** 5. **Item hint text** 6. **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. ```tsx ``` ## 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. ```tsx ``` ### 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. ```tsx ``` A standalone checkbox can carry its own error, for example when it must be ticked to continue: ```tsx ``` ### 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. ```tsx ``` ### Rich labels A `label` accepts any React content, not just a string. Use this to include a [Text Link](/components/text-link) in the label, for example when linking to terms the user is agreeing to. ```tsx Tell me more about reducing my{' '} carbon footprint } /> ``` ### Common form features For validation, error messages, and integration with the `Form` component, see the [Forms pattern guide](/patterns/forms). ## Rules and guidance ### Usage - **Checkbox** MUST NOT be used when only one option from a group may be selected at a time. Rationale: Checkboxes 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. [id: checkbox.not-for-single-select; testability: manual] - **Checkbox** MUST NOT be passed the disabled prop. Rationale: The 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. [id: checkbox.no-disabled; testability: auto] - **Checkbox** SHOULD be replaced with plain text. For example in a SummaryList. When displaying a value the user cannot edit. Rationale: A field that looks interactive but cannot be changed is confusing. Plain text communicates the value without implying editability. [id: checkbox.non-editable-as-text; testability: manual] - **Checkbox** SHOULD NOT be pre-selected by default unless the user has previously chosen the value. Rationale: Pre-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. [id: checkbox.no-preselection; testability: context] ### Content - **Checkbox item label** SHOULD be written in sentence case. Rationale: Sentence case is the standard for all Nebula labels and is easier to scan than title case or all-caps. [id: checkbox.label-sentence-case; testability: manual] - **Checkbox item label** SHOULD use positive framing where possible. Rationale: Negative 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. [id: checkbox.label-positive-framing; testability: manual] - **Checkbox label** SHOULD use first-person phrasing when the checkbox confirms the user's agreement to something. Rationale: '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. [id: checkbox.terms-first-person; testability: manual] - **Checkbox group** SHOULD list options in alphabetical order unless a different order more closely matches users' expectations. Rationale: Alphabetical 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. [id: checkbox.alphabetical-order; testability: manual] - **Checkbox item label** SHOULD NOT end with a comma, semicolon, or full stop. Rationale: Trailing punctuation after checkbox labels adds visual noise. Labels are not sentences in this context. [id: checkbox.label-no-trailing-punctuation; testability: manual] ### Validation - **CheckboxField error text** SHOULD follow the pattern "Select [at least one / the] [noun]" when no option has been selected. Rationale: This 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. [id: checkbox.group-error-message-pattern; testability: manual] - **Checkbox error text** SHOULD NOT be shown at both the group level (CheckboxField) and the individual item level simultaneously. Rationale: Showing 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. [id: checkbox.group-error-not-duplicated; testability: manual] ### Interaction - **Checkbox** WILL set aria-invalid on the native input automatically when an error is present. [id: checkbox.will-aria-invalid; testability: auto] ### Composition - **Checkbox** WILL build aria-describedby automatically, linking the input to its error element and hint text by generated id. [id: checkbox.will-aria-describedby; testability: auto] - **CheckboxField** WILL set aria-required on each child Checkbox automatically, based on the optional prop. [id: checkbox.will-checkboxfield-aria-required; testability: auto] - **Checkbox group** MUST be wrapped in a CheckboxField when two or more Checkbox components share a common purpose. Rationale: CheckboxField 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. [id: checkbox.group-requires-checkboxfield; testability: auto] - **Checkbox** MUST have a unique id prop. Rationale: The 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. [id: checkbox.id-required; testability: auto] ## Accessibility ### 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](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html) [3.3.2 Labels or Instructions](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html) ### 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](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html) [4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html) ### 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 each test covers: - 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. What these statuses mean: - 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. 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. ## Properties ### Checkbox Also accepts all props from `HTMLAttributes`, `InputHTMLAttributes`. - `id` (type `string`, required). Unique `id` for the input. Links the label, hint and error text to it. - `aria-describedby` (type `string`, optional). Id(s) of extra elements that describe the input. When set, this replaces the link to the `hint`; the error is always linked. - `error` (type `ReactNode`, optional). Error message shown below the checkbox. Marks the input as invalid. Falls back to the error for this `name` from the surrounding `Form`. - `label` (type `ReactNode`, required). Text shown next to the checkbox. - `hint` (type `ReactNode`, optional). Helper text shown below the label and linked to the input via `aria-describedby`. - `data-group` (type `boolean`, optional). Set automatically by `CheckboxField` to style the label as part of a group. You don't normally need to set it. - `optional` (type `boolean`, optional). Appends "(optional)" to the label. ### CheckboxField Also accepts all props from `FieldsetHTMLAttributes`, `HTMLAttributes`. - `label` (type `string`, required). Legend text that labels the group of checkboxes. - `hint` (type `ReactNode`, optional). Helper text shown under the label. - `error` (type `ReactNode`, optional). 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` (type `boolean`, optional, default `false`). Shows the field as optional rather than required, and stops its checkboxes being marked `aria-required`. ## Styles Reference only; you do not need this to use the component. This is the CSS its styled elements use, with theme tokens shown by name. `${...}` is an interpolation (a theme token, a mixin or another value from the code). A `&[prop]` or `@when ` block holds CSS that applies only under that condition; these are notation, not valid CSS. ### checkbox-item ```css 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. ```css opacity: 0; width: ${space[5]}; height: ${space[5]}; margin: 0; padding: 0; z-index: 1; cursor: pointer; ``` ### hint-text ```css 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. ```css 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}; } ```