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-checkedbetween"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 noaria-checkedattribute 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>