Checkbox (Tri-State)

Checkbox (Tri-State)

A tri-state checkbox can be checked, not checked, or partially checked. The condition of being partially checked is based on the selection of child elements. If all child elements are selected, the parent checkbox is checked. If some child elements are selected, the parent checkbox is partially checked.



Turn on a screen reader to experience this example in action.

Choose the ingredients for your pizza

HTML Source Code

<fieldset class="checkbox-mixed">
    <legend>Choose the ingredients for your pizza</legend>
    <div role="checkbox" class="group_checkbox" aria-checked="mixed" aria-controls="cond1 cond2 cond3 cond4" tabindex="0"> Select all of the following ingredients</div>
    <ul class="checkboxes">
      <li>
        <label><input type="checkbox" id="cond1">Cheese</label>
      </li>
      <li>
        <label><input type="checkbox" id="cond2" checked="true">Mushrooms</label>
      </li>
      <li>
        <label><input type="checkbox" id="cond3">Olives</label>
      </li>
      <li>
        <label><input type="checkbox" id="cond4">Chicken</label>    
      </li>
    </ul>
  </fieldset>
           

  <!---
This component has been adapted from an example provided by the W3C, in accordance with the W3C Software and Document License https://www.w3.org/copyright/software-license-2023/
-->

JavaScript Source Code

class CheckboxMixed {
    constructor(domNode) {
      this.mixedNode = domNode.querySelector('[role="checkbox"]');
      this.checkboxNodes = domNode.querySelectorAll('input[type="checkbox"]');
  
      this.mixedNode.addEventListener('keydown', this.onMixedKeydown.bind(this));
      this.mixedNode.addEventListener('keyup', this.onMixedKeyup.bind(this));
      this.mixedNode.addEventListener('click', this.onMixedClick.bind(this));
      this.mixedNode.addEventListener('focus', this.onMixedFocus.bind(this));
      this.mixedNode.addEventListener('blur', this.onMixedBlur.bind(this));
  
      for (var i = 0; i < this.checkboxNodes.length; i++) {
        var checkboxNode = this.checkboxNodes[i];
  
        checkboxNode.addEventListener('click', this.onCheckboxClick.bind(this));
        checkboxNode.addEventListener('focus', this.onCheckboxFocus.bind(this));
        checkboxNode.addEventListener('blur', this.onCheckboxBlur.bind(this));
      }
  
      this.updateMixed();
    }
  
    updateMixed() {
      var count = 0;
  
      for (var i = 0; i < this.checkboxNodes.length; i++) {
        if (this.checkboxNodes[i].checked) {
          count++;
        }
      }
  
      if (count === 0) {
        this.mixedNode.setAttribute('aria-checked', 'false');
      } else {
        if (count === this.checkboxNodes.length) {
          this.mixedNode.setAttribute('aria-checked', 'true');
        } else {
          this.mixedNode.setAttribute('aria-checked', 'mixed');
        }
      }
    }
    setCheckboxes(value) {
      for (var i = 0; i < this.checkboxNodes.length; i++) {
        var checkboxNode = this.checkboxNodes[i];
  
        switch (value) {
          case 'last':
            break;
  
          case 'true':
            checkboxNode.checked = true;
            break;
  
          default:
            checkboxNode.checked = false;
            break;
        }
      }
      this.updateMixed();
    }
  
    toggleMixed() {
      var state = this.mixedNode.getAttribute('aria-checked');
  
      if (state === 'false') {
          this.setCheckboxes('true');
      } else {
        if (state === 'mixed') {
          this.setCheckboxes('true');
        } else {
          this.setCheckboxes('false');
        }
      }
  
      this.updateMixed();
    }
  
    /* EVENT HANDLERS */
  
    // Prevent page scrolling on space down
    onMixedKeydown(event) {
      if (event.key === ' ') {
        event.preventDefault();
      }
    }
  
    onMixedKeyup(event) {
      switch (event.key) {
        case ' ':
          this.toggleMixed();
          event.stopPropagation();
          break;
  
        default:
          break;
      }
    }
  
    onMixedClick() {
      this.toggleMixed();
    }
  
    onMixedFocus() {
      this.mixedNode.classList.add('focus');
    }
  
    onMixedBlur() {
      this.mixedNode.classList.remove('focus');
    }
  
    onCheckboxClick(event) {
      this.updateMixed();
    }
  
    onCheckboxFocus(event) {
      event.currentTarget.parentNode.classList.add('focus');
    }
  
    onCheckboxBlur(event) {
      event.currentTarget.parentNode.classList.remove('focus');
    }
  }
  
  // Initialize mixed checkboxes on the page
  window.addEventListener('load', function () {
    let mixed = document.querySelectorAll('.checkbox-mixed');
    for (let i = 0; i < mixed.length; i++) {
      new CheckboxMixed(mixed[i]);
    }
  });

CSS Source Code

/*
  Checkbox (Tri-State) — Restyled
  Deque University ARIA Component

  This component has been adapted from an example provided by the W3C,
  in accordance with the W3C Software and Document License
  https://www.w3.org/copyright/software-license-2023/
*/

:root {
  --dqu-interactive: #2e5f7a;
  --dqu-interactive-light: rgba(46, 95, 122, 0.08);
  --dqu-text-primary: #21201e;
  --dqu-font-family: "Noto Sans", sans-serif;
}

.checkbox-mixed {
  font-family: var(--dqu-font-family);
  color: var(--dqu-text-primary);
}

.checkbox-mixed legend {
  font-family: var(--dqu-font-family);
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--dqu-text-primary);
  padding: 0 4px;
}

.checkbox-mixed ul.checkboxes {
  list-style: none;
  margin: 0;
  padding: 0;
}

.checkbox-mixed ul.checkboxes li {
  margin: 0;
  padding: 0;
  padding-left: 15px;
}

.checkbox-mixed label {
  margin: 1px;
  padding: 4px;
  font-family: var(--dqu-font-family);
  font-size: 1rem;
  color: var(--dqu-text-primary);
  border: 2px solid transparent;
  border-radius: 4px;
}

.checkbox-mixed [role="checkbox"] {
  display: inline-block;
  padding: 4px;
  cursor: pointer;
  font-family: var(--dqu-font-family);
  font-size: 1rem;
  color: var(--dqu-text-primary);
  border: 2px solid transparent;
  border-radius: 4px;
}

.checkbox-mixed [role="checkbox"] {
  padding: 4px 8px;
}

.checkbox-mixed [role="checkbox"]::before {
  position: relative;
  top: 1px;
  left: -2px;
  vertical-align: middle;
  content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='18' width='18' style='forced-color-adjust: auto;'%3E%3Crect x='1' y='1' height='16' width='16' rx='2' stroke='%232e5f7a' stroke-width='1.5' fill-opacity='0' /%3E%3C/svg%3E");
}

.checkbox-mixed [role="checkbox"][aria-checked="true"]::before {
  position: relative;
  top: 1px;
  content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='18' width='18' style='forced-color-adjust: auto;'%3E%3Crect x='1' y='1' height='16' width='16' rx='2' stroke='%232e5f7a' stroke-width='1.5' fill-opacity='0' /%3E%3Cpolyline points='4,9 7,13 13,5' fill='none' stroke='%232e5f7a' stroke-width='2' /%3E%3C/svg%3E");
}

.checkbox-mixed [role="checkbox"][aria-checked="mixed"]::before {
  position: relative;
  top: 1px;
  content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='18' width='18' style='forced-color-adjust: auto;'%3E%3Crect x='1' y='1' height='16' width='16' rx='2' stroke='%232e5f7a' stroke-width='1.5' fill-opacity='0' /%3E%3Cline x1='5' y1='9' x2='13' y2='9' stroke='%232e5f7a' stroke-width='2' /%3E%3C/svg%3E");
}

/* Native checkbox children */
.checkbox-mixed input[type="checkbox"] {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  accent-color: var(--dqu-interactive);
  cursor: pointer;
}

/* Align label text and checkbox vertically */
.checkbox-mixed label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  width: fit-content;
}

.checkbox-mixed [role="checkbox"] {
  outline: none;
}

/* Override library green — teal hover/focus ring matching design system */
.checkbox-mixed input[type="checkbox"]:hover,
.checkbox-mixed input[type="checkbox"]:focus,
.checkbox-mixed input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--dqu-interactive) !important;
  outline-offset: 2px;
}

.checkbox-mixed [role="checkbox"]:focus,
.checkbox-mixed [role="checkbox"]:focus-visible,
.checkbox-mixed [role="checkbox"]:hover {
  border: 2px solid var(--dqu-interactive) !important;
  background-color: var(--dqu-interactive-light);
  outline: none !important;
}

.checkbox-mixed label.focus,
.checkbox-mixed label:hover {
  border: 2px solid var(--dqu-interactive) !important;
  background-color: var(--dqu-interactive-light);
  cursor: pointer;
  outline: none !important;
}

Copy and Paste Full Page Example