WCAG 2.1 AA • AODA compliant • Updated April 12, 2025
A checkbox (<input type="checkbox">) allows users to select one or more options from a set. Each checkbox has a binary state (checked/unchecked); some designs also use an indeterminate visual state for “partially selected” sets.
Where and how is it used? Common use cases include:
This section outlines WCAG criteria, ARIA roles and attributes, keyboard accessibility, and focus management.
| Requirement | WCAG Success Criteria | Description for Designers & Developers |
|---|---|---|
| Programmatic relationships | 1.3.1 Info and Relationships (A) | Each checkbox is associated with a visible label via <label for> / id or by wrapping the input inside the label. Related groups use <fieldset> and <legend>. |
| Labels or Instructions | 3.3.2 Labels or Instructions (A) | Provide clear, descriptive labels. Include help text or aria-describedby when additional guidance is required. |
| Keyboard Operability | 2.1.1 Keyboard (A) | Checkbox receives focus with Tab and toggles with Space. Focus order follows the visual reading order. |
| Focus Visible | 2.4.7 Focus Visible (AA) | Provide a clear visual focus indicator when the checkbox or its label is focused. |
| Name, Role, Value | 4.1.2 Name, Role, Value (A) | Native checkboxes expose role “checkbox,” name (from label), and state (checked true/false; optionally mixed for tri‑state custom controls). |
| Contrast & Target Size | 1.4.3 Contrast (AA), 2.5.5 Target Size (AAA recommended) | Ensure text and icon contrast ≥ 4.5:1 and provide a comfortable hit area (≈ 44×44 CSS px) by making the label clickable. |
| ARIA Role/Attribute | Used On | Purpose & Usage |
|---|---|---|
<input type="checkbox"> |
Native checkbox | Preferred element. Implicit role “checkbox”; exposes checked state and supports keyboard by default. |
role="checkbox" |
Custom element (<div>/<span>) |
Use only when native control cannot be used. Must manage tabindex="0", aria-checked, and keyboard handlers for Space. |
aria-checked="true|false|mixed" |
Custom or tri‑state checkboxes | Announces checked/unchecked/mixed state. Keep visual state in sync. For native inputs, use the DOM indeterminate property for mixed. |
aria-labelledby / aria-label |
Icon‑only or complex labels | Provides accessible name when the visible text is not directly associated. |
aria-describedby |
Checkbox with help/error text | Associates explanatory text (e.g., “We’ll only email important updates”). |
<fieldset> / <legend> |
Groups of related checkboxes | Conveys group relationship and provides a shared accessible name for the set. |
<!-- Native checkbox with label -->
<input type="checkbox" id="news" name="news">
<label for="news">Send me product updates</label>
<!-- Grouped checkboxes with fieldset/legend -->
<fieldset>
<legend>Notification channels</legend>
<label><input type="checkbox" name="ch" value="email"> Email</label>
<label><input type="checkbox" name="ch" value="sms"> SMS</label>
<label><input type="checkbox" name="ch" value="push"> Push notifications</label>
</fieldset>
<!-- "Select all" (tri‑state) -->
<label><input type="checkbox" id="all"> Select all</label>
<label><input type="checkbox" class="item"> Item 1</label>
<label><input type="checkbox" class="item"> Item 2</label>
<script>
const all = document.getElementById('all');
const items = Array.from(document.querySelectorAll('.item'));
function syncAll() {
const checked = items.filter(i => i.checked).length;
all.indeterminate = checked > 0 && checked < items.length;
all.checked = checked === items.length;
}
items.forEach(i => i.addEventListener('change', syncAll));
all.addEventListener('change', () => items.forEach(i => i.checked = all.checked));
syncAll();
</script>
<!-- Custom checkbox (only when native cannot be used) -->
<div role="checkbox" aria-checked="false" tabindex="0" id="cbxCustom">Email me weekly tips</div>
<script>
const c = document.getElementById('cbxCustom');
function toggle(e) {
const v = c.getAttribute('aria-checked') === 'true';
c.setAttribute('aria-checked', String(!v));
}
c.addEventListener('click', toggle);
c.addEventListener('keydown', (e) => { if (e.code === 'Space') { e.preventDefault(); toggle(); } });
</script>
<input type="checkbox"> wherever possible.for/id or wrap the input inside the label to enlarge the hit area.<fieldset> and <legend>.indeterminate property for “Select all” tri‑state patterns.aria-describedby.role, aria-checked, Space to toggle, focus).fieldset/legend for grouped options; AT users won’t know the set’s purpose.aria-checked on native inputs (use the property/state); reserve ARIA for custom controls.aria-checked on custom widgets.aria-describedby / inline feedback for required/invalid states.Test with NVDA/JAWS/VoiceOver and keyboard on Chrome/Firefox/Safari.
| Test Item | WCAG Criteria | Pass Criteria |
|---|---|---|
| Label Programmatically Associated | 1.3.1 Info and Relationships (A), 3.3.2 (A) | Clicking the label toggles the checkbox; AT announces the label as the control’s name. |
| Keyboard Focus & Toggle | 2.1.1 Keyboard (A), 2.4.7 Focus Visible (AA) | Each checkbox is reachable by Tab; Space toggles state; focus indicator is visible. |
| Group Name | 1.3.1 Info and Relationships (A) | <fieldset>/<legend> announce the set name to AT for related options. |
| State Announcement | 4.1.2 Name, Role, Value (A) | Checked/unchecked (and mixed if applicable) is announced correctly when toggled. |
| Tri‑state Logic | 4.1.2 (A) | “Select all” becomes indeterminate when some but not all items are checked; becomes checked when all are checked; unchecked when none. |
| Contrast & Target Size | 1.4.3 Contrast (AA) | Text/icon contrast ≥ 4.5:1; label provides a ~44×44px click/tap target. |