Persona

Checkbox

Inputs · 11 props · Updated Sep 22, 2026

Checkbox is a native <input type="checkbox"> with a label beside it and a painted box standing in for the one the browser draws. One checkbox answers one question. It is also the only control in PDS that can report a group as partly answered.

Which component you render depends on how many boxes there are: CheckboxField for one, CheckboxGroupField for a set. The difference is not cosmetic, and the section below says what goes wrong when a set is composed by hand.

  • Container holds the box and its label together, and is the click target for both.
  • Box the square that carries the answer. One box, three states: empty, checked, and the dash that means indeterminate.
  • Label the option, in words. It is the checkbox’s accessible name, so it is not optional.

The painted box is aria-hidden and the real input sits on top of it, transparent. State arrives as a shape before it arrives as a color: a check for checked, a dash for indeterminate, an empty box for neither, so the three states differ in more than their fill.

Nothing happens when a checkbox is clicked. It is a real form input, carrying a value and emitting a change event and posting with the form it sits in, so the answer is recorded now and takes effect on submit. That is what suits it to a question rather than to a switch: there is no confirmation step to design, and no feedback owed beyond the check appearing.

Radio is the control where only one of the options may be chosen at a time, a Toggle where the change should apply the moment it is switched, and a Combobox where the list is long enough that the reader would rather search it than read it.

CheckboxField has two label props, and you want one of them

Section titled “CheckboxField has two label props, and you want one of them”

CheckboxField is a checkbox with a FormField around it, and it splits the label in two: label renders above the box, which is where a question goes, and checkboxLabel renders beside it, which is where an option goes. A single checkbox usually wants one or the other.

Pass both and the checkbox gets two <label> elements pointing at the same input, because the checkbox takes its id from the field’s context and its own label uses that same id. A screen reader then reads the question and the option as one run-on name. Use label when the text is a question the box answers, and checkboxLabel when it is the thing being agreed to.

Six of the twelve field props do not reach the field. CheckboxField forwards label, checkboxLabel, helperText, warningText, errorText, configField and labelProps, and spreads the rest onto the <input>, so inputId, rightActions, configFieldInputWidth, helperProps, warningProps and errorProps are accepted and then dropped. They appear in the props table because they are in the type.

Three checkboxes in one FormField share one id

Section titled “Three checkboxes in one FormField share one id”

A FormField hands one input id to everything inside it, and that id wins over a checkbox’s own. So three checkboxes hand-composed into one FormField render three identical ids, every label’s for attribute points at the first box, and clicking the second label toggles the first. A screen reader announces all three with the first one’s label.

CheckboxGroupField exists for this. It clears the id before rendering its children, so each box generates its own, and it wires the group on top of that: React Aria’s checkbox group gives the set a role="group", holds the selection as an array of strings in value and onChange, and derives each child’s checked from it. disabled, readOnly and required on the group reach every box in it.

It is also the one composed field whose error message is announced. React Aria generates the ids for the helper and error text and lists both in the group’s aria-describedby, which is the association FormField otherwise leaves to you.

The children have to be checkbox elements. The group clones each one to inject its state, so a checkbox wrapped in a layout div is a child the group cannot reach. That is also the case where a hand-composed FormField is still right: a field holding a checkbox and something else, where the ids are yours to keep distinct.

One radio or toggle in a column of checkboxes changes what the column means

Section titled “One radio or toggle in a column of checkboxes changes what the column means”

A column of checkboxes tells the reader the answers are independent, and one radio or toggle dropped into it withdraws that promise without saying so. If exactly one option may be chosen, the whole group is Radio instead.

Reserve indeterminate for the parent of a group

Section titled “Reserve indeterminate for the parent of a group”

indeterminate shows a dash rather than a check and sets aria-checked="mixed", which is the state of a parent whose children are only partly selected. No other PDS control can say it: Toggle’s value is a boolean, and a radio group’s state is which member, not how many.

The dash is display only. It is written onto the DOM node in an effect and never touches checked, so a parent rendered indeterminate still submits and reports whatever checked says. Derive the parent’s checked from its children and use indeterminate for the third case; do not let the dash stand in for a value.

Choose controlled or uncontrolled on the first render

Section titled “Choose controlled or uncontrolled on the first render”

Pass checked and the checkbox is yours to update. Omit it, pass defaultChecked, and the checkbox keeps its own state. Which mode it is in is latched the first time it renders, so a checkbox mounted with checked still undefined while its data loads stays uncontrolled for its whole life, and later passing a real boolean will not move it. Render it once the value exists, or give it a key that changes with the value.

Split the option and its elaboration between label and tooltip

Section titled “Split the option and its elaboration between label and tooltip”

tooltip wraps the label in a tooltip and underlines it with a dashed rule, so the label advertises that there is more to read. It hangs off the label element rather than the input, which makes it an aside rather than part of what the checkbox announces. Put the option in the label and the elaboration in the tooltip, never the other way round.

A checkbox is named by its label, or by a heading you point at

Section titled “A checkbox is named by its label, or by a heading you point at”

label renders a real <label> pointed at the input, using the id you passed or a generated one. Inside Form Field the field’s own input id wins over the id prop, so the checkbox adopts the field’s label instead of carrying its own. CheckboxGroupField is the one wrapper that gives the id back, which is why a set belongs in it.

Where the visible label lives somewhere else entirely, a column header above a column of checkboxes, point aria-labelledby at it. A checkbox with neither a label nor an aria-labelledby reaches a screen reader as a checkbox with no name, and the row it sits in is not read out with it.

How to word the label itself is on Writing.

Three components, three tables. CheckboxField is a checkbox’s props plus the twelve FormField carries, plus checkboxLabel:

NameType
checkboxLabel

ReactNode

label

ReactNode

Renders <Label> above input

inputId

string

ID of the input element. Providing this will automatically link the label to the input.

helperText

ReactNode

Renders <HelperText> below input

errorText

ReactNode

Renders <ErrorText> below input and helper text.

warningText

ReactNode

Renders <WarningText> below input and helper text.

labelProps

FormLabelProps

helperProps

(HelperTextProps & RefAttributes<HTMLDivElement>)

warningProps

(Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, “ref”> & RefAttributes<HTMLDivElement>)

errorProps

(Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, “ref”> & RefAttributes<HTMLDivElement>)

configField

boolean

Whether to render the form field as a config field. This will render the helper text as a tooltip.

configFieldInputWidth

string

Override the width of the input area when configField is true. Accepts any valid CSS width value (e.g. ‘33%’, ‘66.67%’). Defaults to ‘50%‘.

rightActions

ReactNode

Renders actions to the right of the label.

id

string

id for the underlying <input>. If not provided a unique ID is generated automatically and linked to the label.

onChange

((e: ChangeEvent<HTMLInputElement, Element>) => void)

Called whenever the checked state changes.

disabled

boolean

Disables interaction and renders the checkbox in a muted style.

indeterminate

boolean

When true the checkbox displays a dash (−) instead of a tick, indicating a mixed / partially-selected state. Sets aria-checked=“mixed” for screen readers.

checked

boolean

Controlled checked state. Omit (or pass undefined) to let the component manage state internally via defaultChecked.

value

unknown

Value submitted with a form.

data-test

string

containerProps

Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, “ref”>

Additional props forwarded to the outer container <div>.

size

md, sm

Size of the checkbox and its label text.

tooltip

ReactNode

Tooltip content to display when hovering over the label. When provided, the label will be wrapped in a tooltip.

Also accepts every prop of Omit<ComponentPropsWithoutRef<‘input’>, ‘value’ | ‘size’>.

CheckboxGroupField is the shortest of the three, because most of what it does comes from React Aria’s checkbox group rather than from props of its own — value, onChange and the rest arrive through the base type at the bottom of the table. It has no configField:

NameType
children

ReactElement<CheckboxProps, string | JSXElementConstructor<any>>[]

disabled

boolean

readOnly

boolean

required

boolean

label

ReactNode

Renders <Label> above input

inputId

string

ID of the input element. Providing this will automatically link the label to the input.

helperText

ReactNode

Renders <HelperText> below input

errorText

ReactNode

Renders <ErrorText> below input and helper text.

warningText

ReactNode

Renders <WarningText> below input and helper text.

labelProps

FormLabelProps

helperProps

(HelperTextProps & RefAttributes<HTMLDivElement>)

warningProps

(Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, “ref”> & RefAttributes<HTMLDivElement>)

errorProps

(Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, “ref”> & RefAttributes<HTMLDivElement>)

configFieldInputWidth

string

Override the width of the input area when configField is true. Accepts any valid CSS width value (e.g. ‘33%’, ‘66.67%’). Defaults to ‘50%‘.

rightActions

ReactNode

Renders actions to the right of the label.

Also accepts every prop of Omit< AriaCheckboxGroupProps, ‘description’ | ‘disabled’ | ‘isReadOnly’ | ‘isRequired’ | ‘errorMessage’ >.

Checkbox’s own props, which CheckboxField also accepts and CheckboxGroupField expects on each child:

NameType
id

string

id for the underlying <input>. If not provided a unique ID is generated automatically and linked to the label.

onChange

((e: ChangeEvent<HTMLInputElement, Element>) => void)

Called whenever the checked state changes.

label

ReactNode

Visible label rendered to the right of the checkbox box.

disabled

boolean

Disables interaction and renders the checkbox in a muted style.

indeterminate

boolean

When true the checkbox displays a dash (−) instead of a tick, indicating a mixed / partially-selected state. Sets aria-checked=“mixed” for screen readers.

checked

boolean

Controlled checked state. Omit (or pass undefined) to let the component manage state internally via defaultChecked.

value

unknown

Value submitted with a form.

data-test

string

containerProps

Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, “ref”>

Additional props forwarded to the outer container <div>.

size

md, sm

Size of the checkbox and its label text.

tooltip

ReactNode

Tooltip content to display when hovering over the label. When provided, the label will be wrapped in a tooltip.

Also accepts every prop of Omit<ComponentPropsWithoutRef<‘input’>, ‘value’ | ‘size’>.

Was this page helpful?