Checkbox
A hand-drawn checkbox with an indeterminate state and a CheckboxGroup wrapper.
Overview
A Checkbox is a boolean input — on or off — that can also show an indeterminate (“some but not
all”) state. CheckboxGroup groups related checkboxes and manages their combined value as an
array.
Anatomy
A sketchy box drawn behind a real <input type="checkbox"> (React/Web Components) or a Pressable
with accessibilityRole="checkbox" (React Native). When checked or indeterminate, a hand-drawn
check or minus mark renders on top — reusing the existing Icon/gh-icon component rather than
inventing new glyph geometry.
Variants & States
Checkbox has no visual variants — only states.
| State | Status | Notes |
|---|---|---|
| Unchecked | Implemented | — |
| Checked | Implemented | Box fills solid; a check mark renders. |
| Indeterminate | Implemented | A dash mark renders instead of a check. DOM-only property, not a form value. |
| Disabled | Implemented | — |
| Loading | Not implemented | — |
Usage
Do
- Use CheckboxGroup when several checkboxes represent one multi-select field.
- Use indeterminate for a "select all" checkbox when some, but not all, children are checked.
Don't
- Don't use a single Checkbox to represent a binary choice better served by a Switch (an immediate settings toggle) — Checkbox implies a form field, Switch implies an instant state change.
- Don't rely on indeterminate to persist across a checked toggle — setting checked clears it, matching native checkbox behavior.
Accessibility
- React: a real
<input type="checkbox">carries native keyboard (Space toggles) and focus behavior.indeterminateis a DOM-only property (not an HTML attribute or form value) — set imperatively on the underlying input, exactly as native browsers/other checkbox implementations do. - Web Components:
gh-checkboxrenders a real<input type="checkbox">in its shadow DOM for native behavior, and bridges checked state to real<form>participation viaElementInternals(form value present only when checked). It also exposes:state(checked)/:state(indeterminate)CSS pseudo-classes viaElementInternals.states, alongside the reflectedchecked/indeterminateattributes. - React Native:
accessibilityRole="checkbox"+accessibilityState.checked('mixed'when indeterminate) for native platforms, plus a directaria-checkedprop for the web target (React Native Web does not derivearia-checkedfromaccessibilityStatethe way it doesaria-disabled). - See the Accessibility Guide for general conventions.
Content
The label names what’s being opted into (“Subscribe to updates”), not an action — contrast with Button.
Props API
React@ghds/react
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Visible label, associated via Radix Label. |
indeterminate | boolean | — | Tri-state visual indicator. Set imperatively as a DOM property. |
value | string | — | Identifies this checkbox within a CheckboxGroup. Ignored standalone. |
checked | boolean | — | Controlled checked state. |
defaultChecked | boolean | false | Initial checked state when uncontrolled. |
disabled | boolean | false | Disables interaction. |
...rest | InputHTMLAttributes | — | All other native input attributes (onChange, name, etc.) pass through. |
Web Components@ghds/web-components
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | false | Reflected attribute. |
indeterminate | boolean | false | Reflected attribute. Also set as a DOM property on the internal input. |
disabled | boolean | false | Disables interaction and form participation. |
name | string | — | Submitted control name. |
value | string | 'on' | Submitted value when checked. |
label | string | — | Optional visible label, also the accessible name. |
React Native@ghds/react-native
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Visible, accessible label — required (no separate label element on RN). |
checked | boolean | — | Controlled checked state. |
defaultChecked | boolean | — | Initial checked state when uncontrolled. |
onCheckedChange | (checked: boolean) => void | — | Fires with the next checked state on toggle. |
indeterminate | boolean | — | Tri-state visual indicator. |
disabled | boolean | false | Disables interaction. |
value | string | — | Identifies this checkbox within a CheckboxGroup. |
testID | string | — | Test handle for queries. |
Code examples
// React — @ghds/react (onChange receives an event; read event.target.checked)
import { Checkbox } from '@ghds/react/checkbox';
<Checkbox
label="Subscribe to updates"
checked={subscribed}
onChange={(event) => setSubscribed(event.target.checked)}
/>;
<Checkbox label="Select all" indeterminate />;
<!-- Web Components — @ghds/web-components (no callback prop; listen for the `change` event) -->
<script type="module">
import '@ghds/web-components/checkbox';
</script>
<gh-checkbox label="Subscribe to updates"></gh-checkbox>
<gh-checkbox label="Select all" indeterminate></gh-checkbox>
// React Native — @ghds/react-native (onCheckedChange receives a bare boolean, not an event)
import { Checkbox } from '@ghds/react-native/checkbox';
<Checkbox label="Subscribe to updates" checked={subscribed} onCheckedChange={setSubscribed} />;
Live Demo
React and Web Components render live below. React Native can’t run in a browser without additional
react-native-web wiring (not set up in this site), so its usage is shown as a code sample instead.