EDS 0.x
Components

Checkbox

Examples

Sizes

Figma size sets: Sm 16px · Md 20px.

States

Interaction states from Figma: Default, Hover, Focus, Disabled, Read-only — across Unchecked, Checked, and Indeterminate.

Validation

Figma Validation=Invalid (Negative) — use validation="error". Unchecked uses a light negative fill; checked/indeterminate use solid negative.

Checkbox with Label

CheckboxOption is Figma Checkbox.Option — label required, optional description.

Option Group — Vertical

Option Group — Horizontal

Controlled

Unchecked

Group with Controlled Value

Selected: none

TanStack Form

Submit without accepting terms to see validation. Checking the box and submitting again succeeds.

RTL

Wrap the control (or a parent) in dir="rtl" / lang="fa". Option labels and descriptions align to the inline-start edge.

API Reference

Checkbox

PropTypeDefaultDescription
size"sm" | "md""sm"Box size — Sm 16px / Md 20px
validation"none" | "error""none"Figma Validation (error = Invalid Negative)
checkedbooleanControlled checked state
defaultCheckedbooleanfalseInitial checked state (uncontrolled)
indeterminatebooleanfalseMixed/indeterminate state
disabledbooleanfalseDisables interaction
readOnlybooleanfalsePrevents toggling (disabled surface)
requiredbooleanfalseMarks as required for form validation
namestringForm field name
valuestringValue submitted with the form
onCheckedChange(checked: boolean) => voidCalled when toggled

CheckboxOption

PropTypeDefaultDescription
labelReactNodeLabel text (required)
descriptionReactNodeHelper text below the label
All Checkbox propsPassed through to the inner Checkbox

CheckboxOptionGroup

PropTypeDefaultDescription
direction"vertical" | "horizontal""vertical"Layout direction
valuestring[]Controlled selected values
defaultValuestring[]Initial values (uncontrolled)
onValueChange(value: string[]) => voidCalled when selection changes
allValuesstring[]All possible values (for parent checkbox)
disabledbooleanfalseDisables all checkboxes in the group

Accessibility

WCAG success criteria

CriterionLevelHow EDS helps
2.1.1 KeyboardASpace and Enter toggle; Tab reaches every enabled control; disabled controls are inert
2.4.7 Focus VisibleAA:focus-visible ring with 2px offset outside the control
1.3.1 Info and RelationshipsACheckboxOption associates label + control; groups use role="group" with an accessible name
4.1.2 Name, Role, ValueACheckbox role, checked/mixed/disabled states, and form name/value exposed to assistive tech
1.4.3 Contrast (Minimum)AALabel and description text use default foreground tokens on muted surfaces
1.4.11 Non-text ContrastAABox border, fill, check icon, and error styling meet 3:1 against adjacent colors
3.3.1 Error IdentificationAvalidation="error" styles the control; consumers render descriptive error text
3.3.2 Labels or InstructionsACheckboxOption requires a label; groups require aria-label / aria-labelledby
2.5.8 Target Size (Minimum)AACheckboxOption label expands the clickable region; bare checkboxes need adequate surrounding spacing or an associated label

Why it matters

Checkboxes gate consent, permissions, and multi-select filters — mistakes here block purchases, hide required settings, or silently change user preferences. Keyboard-only users, screen-reader users, and people with motor impairments rely on a predictable toggle, a visible focus indicator, and error feedback that does not depend on color alone. Indeterminate “select all” states must communicate partial selection clearly; otherwise bulk actions feel broken or untrustworthy.

Keyboard

KeyContextBehavior
⇥ Tab / + ⇥ TabAny checkboxMoves focus to the next/previous focusable control in document order
SpaceFocused, enabled checkboxToggles checked ↔ unchecked (Base UI checkbox)
↵ EnterFocused, enabled checkboxToggles checked ↔ unchecked (form submit contexts)
Space / ↵ EnterdisabledNo toggle — control is removed from the tab order when disabled
Space / ↵ EnterreadOnlyNo toggle — control stays focusable but state does not change
⇥ TabInside CheckboxOptionGroupMoves between individual checkboxes; each option is independently focusable

Groups do not use arrow-key roving focus — unlike radios, multiple checkboxes in a group may all be checked, so each option keeps its own tab stop.

Screen readers & semantics

SurfaceSemanticsNotes
CheckboxNative role="checkbox" via Base UIExposes checked, mixed (indeterminate), and disabled states
CheckboxOption<label> wrapping control + textLabel text becomes the accessible name; no extra aria-label needed when label is visible
Bare CheckboxRequires aria-label, aria-labelledby, or visible labelIcon-only or standalone boxes must not ship unlabeled
CheckboxOptionGrouprole="group"Provide aria-label or aria-labelledby naming the set (e.g. “Notifications”)
Check / minus iconsaria-hidden="true"Decorative — state is announced from the checkbox role
validation="error"Visual styling onlyPair with error text (role="alert") and aria-describedby pointing at the message
indeterminatemixed stateUsed for parent “select all” rows when some children are checked

Descriptions on CheckboxOption render as visible helper text; associate them with aria-describedby when they convey essential instructions, not just marketing copy.

Focus & visuals

  • Focus ring: :focus-visible draws a 2px gap + 2px stroke outside the box via a ::after pseudo-element, using --eds-color-outline-primary (overridable with --eds-component-checkbox-focus-ring). Matches other EDS form controls.
  • Contrast: Checked and indeterminate fills use semantic tokens (--eds-color-bg-primary, --eds-color-icon-neutral-inverse); error states use negative tokens so invalid fields remain distinguishable at 3:1 non-text contrast.
  • Target size: Sm (16px) and Md (20px) boxes sit inside clickable <label> regions on CheckboxOption, expanding the hit area beyond the minimum 24×24px touch target where a label is present.
  • RTL: Layout uses logical properties — label and description align to inline-start; no separate LTR-only API.
  • Reduced motion: State transitions respect --eds-motion-duration-*; the checkbox does not rely on animation to convey state.

Build with it

Do

  • Use CheckboxOption (or an external <label htmlFor="…">) for every user-facing choice — never rely on proximity alone.
  • Name groups with aria-label or aria-labelledby on CheckboxOptionGroup.
  • Wire validation: validation="error" on the control + visible error copy with role="alert" and aria-describedby.
  • Use indeterminate only for true partial-selection (“select all” with some children checked); clear it when all/none are selected.
  • Keep readOnly for display-only checked states that must remain focusable for context.

Don't

  • Ship bare <Checkbox /> without aria-label or a visible label.
  • Use color alone to signal errors — always pair validation="error" with text.
  • Disable a checkbox to mean “read-only” — use readOnly so assistive tech still announces the value.
  • Hide required checkboxes off-screen while keeping them in the tab order — use visible labels and instructions.

Quick test

  1. Keyboard-only (2 min): Tab to each checkbox in a group; toggle with Space and Enter; confirm disabled boxes skip and readOnly boxes do not change.
  2. VoiceOver / NVDA (2 min): Verify each option announces “checkbox, checked/unchecked/mixed, disabled”; confirm the group name is read when entering the set.
  3. Error path (1 min): Submit a TanStack Form without checking a required box — error text appears, control gets error styling, and the message is linked via aria-describedby or read after focus.
  4. Zoom & contrast (1 min): At 200% zoom, focus ring remains outside the box; error and checked states are still distinguishable without color alone.
  5. RTL (30 sec): Set dir="rtl" — label sits on the inline-start side and reading order feels natural.

On this page