Standards · ARIA

Role Widget

checkbox

Marks an element as a two- or three-state checkbox. Use <input type="checkbox"> first; only reach for role="checkbox" when you cannot use the native input — for example when building a tri-state control that must show a mixed value.

When to use

Use <input type="checkbox">. Native gives you focus, Space activation, the :checked pseudo-class, form submission, and the indeterminate JS property for tri-state. role="checkbox" exists for two cases:

  • You need a tri-state checkbox exposed as aria-checked="mixed" (a “select all” that reflects child state).
  • A design system ships a custom checkbox component you cannot replace.

If you implement role="checkbox" on a non-input element, you must set aria-checked (it is required), manage tabindex="0", and handle keydown for Space.

Keyboard + focus contract

Per the APG checkbox pattern:

  • Tab moves focus to the checkbox.
  • Space toggles aria-checked between "true" and "false" (and "mixed" for tri-state).
  • Enter does NOT activate a checkbox — Space only. (Native inputs also ignore Enter in most browsers.)

Focus stays on the checkbox after activation.

Common failures

  • role="checkbox" with no aria-checked attribute set. The state is required from the moment the element is in the DOM.
  • Using aria-checked="true" as a class hook only — toggling the visual but never updating the attribute on click.
  • Custom checkboxes that respond to click but not to Space.
  • Wrapping a native <input> inside a <div role="checkbox"> — duplicate semantics confuse screen readers.
  • Tri-state controls that cycle Space through false → true → mixed → false. APG specifies false → true → false; mixed is set programmatically by the page, not the user.

Example

<!-- Preferred -->
<label>
  <input type="checkbox" name="terms" required>
  I agree to the terms
</label>

<!-- Custom tri-state "select all" -->
<div
  role="checkbox"
  tabindex="0"
  aria-checked="mixed"
  aria-labelledby="selectAllLabel"
>
</div>
<span id="selectAllLabel">Select all rows</span>