Checkbox
The Checkbox component provides a customizable, accessible checkbox input. It’s built on top of the Radix UI Checkbox primitive with additional styling, and can render an optional label and caption alongside the box.
Installation
import { Checkbox } from "@harnessio/ui/components";
function Example() { const [checked, setChecked] = React.useState(false);
return ( <Checkbox id="accept-terms" checked={checked} onCheckedChange={setChecked} label="Accept terms and conditions" caption="You must accept terms and conditions to proceed." /> );}The checkbox works controlled (with checked + onCheckedChange) or uncontrolled (with defaultChecked). Set checked="indeterminate" for a partial-selection state.
Anatomy
Checkbox is a single component — you configure it entirely through props, not by composing parts:
<Checkbox checked={checked} onCheckedChange={setChecked} label="Accept terms and conditions" caption="You must accept terms and conditions to proceed."/>Internally it renders:
- The box — the Radix checkbox itself, with an indicator that shows a check for the checked state and a minus for
indeterminate. It picks up theerrorstyling whenerroris set. - Label — when you pass
label, a Label is rendered and wired to the box withhtmlFor, so clicking the text toggles the checkbox. A stableidis generated automatically if you don’t supply one. - Caption — when you pass
caption, a helper line renders beneath the label.
The label and caption block only appears if you pass label or caption; otherwise just the box renders (give it an aria-label in that case — see accessibility).
Best practices
Do
- Give every checkbox an accessible name — a
labelfor the common case, or anaria-labelwhen the box stands alone. - Use the
indeterminatestate for a parent checkbox that summarizes a mix of checked and unchecked children. - Keep the
labelto a short, clear statement of what checking the box means, and usecaptionfor the supporting detail.
Don’t
- Don’t use a checkbox for mutually exclusive choices — use Radio instead.
- Don’t rely on the
errorstyling alone to communicate a problem; pair it with a visible, associated message (see the accessibility notes). - Don’t write labels as questions (“Accept terms?”); phrase them as the affirmative action (“Accept terms and conditions”).
Content
- Write the label as a positive statement of what checking does, in sentence case.
- Keep the caption to one short line of supporting guidance or a format/requirement note.
Accessibility
Checkbox is built on the Radix Checkbox primitive, so it exposes proper checkbox semantics and keyboard support.
Keyboard
- The box is in the tab order and toggles with Space.
- Disabled checkboxes are skipped by keyboard navigation.
Labeling
- When you pass
label, it’s associated with the box viahtmlFor/id(anidis auto-generated if you don’t provide one), so the text is both clickable and announced as the checkbox’s name. - The
indeterminatestate is exposed asaria-checked="mixed"and shown with a minus icon, distinguishing it from both checked and unchecked.
Focus & states
- The box shows the standard keyboard focus ring, and disabled/error states have distinct styling.
Known limitations
- A checkbox with no
labelhas no accessible name. When you omitlabel, only the box renders, so you must pass anaria-label(oraria-labelledby) yourself — those forward to the underlying checkbox. Without one, screen-reader users hear an unlabeled checkbox. erroris styling only. Theerrorprop turns the box red but sets noaria-invalidand isn’t linked to any message. Thecaptionis visible but not wired up asaria-describedby, so an error conveyed through color/caption alone won’t reach assistive tech. Setaria-invalidand associate an error message yourself, or use the form-field components that handle that wiring.showOptionalLabelis inverted, and its indicator is decorative. By default no required marker shows; passingshowOptionalLabel={false}renders a required asterisk. That asterisk isaria-hidden, so it’s a visual cue only — required state isn’t announced. Mark the field required through your form validation as well.
Properties
Prop | Required | Default | Type |
|---|---|---|---|
| id | false | string | |
| checked | false | false | boolean | 'indeterminate' |
| defaultChecked | false | false | boolean |
| onCheckedChange | false | (checked: boolean | 'indeterminate') => void | |
| disabled | false | false | boolean |
| label | false | string | |
| caption | false | string | |
| captionVariant | false | TextProps['variant'] | |
| error | false | false | boolean |
| showOptionalLabel | false | boolean | |
| truncateLabel | false | true | boolean |
| className | false | string |
Checkbox also forwards the remaining Radix Checkbox props (e.g. name, value, aria-label) onto the underlying checkbox.