Loading Developer Playground

Loading ...

Skip to main content
ARIA ROLEWidget Roles

checkbox

A checkable input that has three possible values: true, false, or mixed. Checkboxes allow users to select multiple options from a set.

Keyboard Support
Space
Required Attribute
aria-checked
Possible States
true, false, mixed

Overview

The checkbox role indicates an interactive control that represents a binary choice (checked or unchecked) or a tri-state control (checked, unchecked, or mixed/indeterminate).

Unlike radio buttons, checkboxes are independent - multiple checkboxes can be checked simultaneously. The mixed state is typically used for parent checkboxes that control a group of child checkboxes.

Use Native <input type="checkbox">

Always prefer the native <input type="checkbox"> element. Only use role="checkbox" when you absolutely must use a different element for styling reasons.

Live Demo: Checkbox States

Single Checkbox

Mixed State (Select All)

Try with keyboard: Tab to focus checkboxes, press Space to toggle. Screen readers will announce the checkbox state (checked, not checked, or mixed).

Code Examples

Basic Checkbox

<!-- Basic Checkbox -->
<div 
  role="checkbox" 
  tabindex="0"
  aria-checked="false"
  onclick="toggleCheckbox(this)"
  onkeydown="handleKeyPress(event, this)"
>
  <span class="checkbox-icon">☐</span>
  I agree to the terms
</div>

<script>
  function toggleCheckbox(element) {
    const isChecked = element.getAttribute('aria-checked') === 'true';
    element.setAttribute('aria-checked', !isChecked);
    
    // Update visual indicator
    const icon = element.querySelector('.checkbox-icon');
    icon.textContent = !isChecked ? '☑' : '☐';
  }
  
  function handleKeyPress(event, element) {
    if (event.key === ' ' || event.key === 'Enter') {
      event.preventDefault();
      toggleCheckbox(element);
    }
  }
</script>

Mixed State (Select All)

<!-- Mixed State Checkbox (Select All) -->
<div 
  role="checkbox"
  tabindex="0"
  aria-checked="mixed"
  onclick="toggleSelectAll()"
  id="select-all"
>
  <span class="checkbox-icon">⊟</span>
  Select All
</div>

<div role="group" aria-labelledby="select-all">
  <div role="checkbox" aria-checked="true" tabindex="0">
    Option 1
  </div>
  <div role="checkbox" aria-checked="false" tabindex="0">
    Option 2
  </div>
  <div role="checkbox" aria-checked="true" tabindex="0">
    Option 3
  </div>
</div>

<script>
  function toggleSelectAll() {
    const parent = document.getElementById('select-all');
    const state = parent.getAttribute('aria-checked');
    const children = document.querySelectorAll('[role="group"] [role="checkbox"]');
    
    // If mixed or unchecked, check all
    // If checked, uncheck all
    const newState = state === 'true' ? 'false' : 'true';
    
    parent.setAttribute('aria-checked', newState);
    children.forEach(child => {
      child.setAttribute('aria-checked', newState);
    });
    
    updateIcon(parent, newState);
  }
</script>

Native HTML Checkbox

<!-- Native HTML Checkbox (Best Practice) -->
<label class="checkbox-label">
  <input 
    type="checkbox" 
    name="subscribe"
    checked
  />
  Subscribe to newsletter
</label>

<!-- Styled Native Checkbox -->
<style>
  .checkbox-label input[type="checkbox"] {
    width: 20px;
    height: 20px;
    cursor: pointer;
    accent-color: #4F46E5; /* Indigo */
  }
</style>

<!-- Use native checkboxes whenever possible! -->

React Components

// React Checkbox Component
import { useState } from 'react';

// Good: Use native checkbox
function NativeCheckbox({ label, checked, onChange }) {
  return (
    <label className="flex items-center gap-2 cursor-pointer">
      <input
        type="checkbox"
        checked={checked}
        onChange={(e) => onChange(e.target.checked)}
        className="w-5 h-5 text-indigo-600"
      />
      <span>{label}</span>
    </label>
  );
}

// Custom checkbox with role (only when necessary)
function CustomCheckbox({ label, checked, onChange }) {
  const handleKeyDown = (e) => {
    if (e.key === ' ' || e.key === 'Enter') {
      e.preventDefault();
      onChange(!checked);
    }
  };

  return (
    <div
      role="checkbox"
      tabIndex={0}
      aria-checked={checked}
      onClick={() => onChange(!checked)}
      onKeyDown={handleKeyDown}
      className="flex items-center gap-2 cursor-pointer"
    >
      <div className={`w-5 h-5 border-2 rounded flex items-center justify-center ${
        checked ? 'bg-indigo-600 border-indigo-600' : 'border-gray-400'
      }`}>
        {checked && (
          <svg className="w-4 h-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
          </svg>
        )}
      </div>
      <span>{label}</span>
    </div>
  );
}

// Mixed state checkbox (Select All)
function SelectAllCheckbox({ items, selectedItems, onSelectAll }) {
  const allSelected = selectedItems.length === items.length;
  const someSelected = selectedItems.length > 0 && selectedItems.length < items.length;
  const checked = allSelected ? true : someSelected ? 'mixed' : false;

  return (
    <div
      role="checkbox"
      tabIndex={0}
      aria-checked={checked}
      onClick={onSelectAll}
      className="flex items-center gap-2 cursor-pointer font-bold"
    >
      <div className="w-5 h-5 border-2 rounded flex items-center justify-center bg-indigo-600 border-indigo-600">
        {checked === true && '✓'}
        {checked === 'mixed' && '−'}
      </div>
      <span>Select All</span>
    </div>
  );
}

Required Attributes

aria-checked

Required. Indicates the checkbox state. Must be one of:

  • "true" - Checkbox is checked
  • "false" - Checkbox is unchecked
  • "mixed" - Checkbox is in mixed/indeterminate state (typically for "Select All")

Keyboard Support

SpaceToggles the checkbox state

Primary method to toggle checkbox. For mixed state checkboxes, Space typically checks all children.

Best Practices

Use native <input type="checkbox"> whenever possible

Always provide a visible label for checkboxes

Make sure checkboxes have sufficient click/tap target size (44x44px minimum)

Use aria-checked="mixed" for parent checkboxes that control child checkboxes

Provide clear visual indicators for all three states (checked, unchecked, mixed)

Group related checkboxes with <fieldset> and <legend> or role="group"

×

Don't use checkbox for mutually exclusive options - use radio buttons

×

Don't forget to add tabindex="0" when using role="checkbox" on divs

×

Don't rely solely on color to indicate checkbox state

×

Don't make checkbox labels clickable separately - the entire control should be clickable

Common Use Cases

Terms and conditions agreement
Multi-select filters
Preference settings
Select all/none controls
Feature toggles
Permission settings
Notification preferences
Form options selection

Accessibility Notes

Screen Reader Announcements

Screen readers will announce "checkbox, checked" or "checkbox, not checked" along with the label. For mixed state, it will announce "checkbox, mixed" or "checkbox, partially checked" depending on the screen reader.

Mixed State Usage

The mixed state should only be used for parent checkboxes when some (but not all) child checkboxes are checked. It indicates a partially selected state and is commonly used in "Select All" functionality.

Focus Management

Checkboxes should be keyboard focusable with tabindex="0". Provide clear visual focus indicators that meet WCAG contrast requirements. Native checkboxes handle this automatically.

Related Roles & Attributes

Specifications & Resources