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 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));
        //checkboxNode.setAttribute('data-last-state', checkboxNode.checked);
      }
  
      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');
          //this.updateCheckboxStates();
        }
      }
    }
  /*
    updateCheckboxStates() {
      for (var i = 0; i < this.checkboxNodes.length; i++) {
        var checkboxNode = this.checkboxNodes[i];
        checkboxNode.setAttribute('data-last-state', checkboxNode.checked);
      }
    }
  
    anyLastChecked() {
      var count = 0;
  
      for (var i = 0; i < this.checkboxNodes.length; i++) {
        if (this.checkboxNodes[i].getAttribute('data-last-state') == 'true') {
          count++;
        }
      }
  
      return count > 0;
    }
  */
    setCheckboxes(value) {
      for (var i = 0; i < this.checkboxNodes.length; i++) {
        var checkboxNode = this.checkboxNodes[i];
  
        switch (value) {
          case 'last':
            //checkboxNode.checked = checkboxNode.getAttribute('data-last-state') === 'true';
            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') {
          /*
        if (this.anyLastChecked()) {
          this.setCheckboxes('last');
        } else {
          this.setCheckboxes('true');
        }
          */
          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) {
      /*
      event.currentTarget.setAttribute(
        'data-last-state',
        event.currentTarget.checked
      ); */
      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-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;
  }
  
  .checkbox-mixed [role="checkbox"] {
    display: inline-block;
    padding: 4px;
    cursor: pointer;
  }
  
  .checkbox-mixed [role="checkbox"]::before {
    position: relative;
    top: 1px;
    content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='16' width='16' style='forced-color-adjust: auto;'%3E%3Crect x='2' y='2' height='13' width='13' rx='2' stroke='currentcolor' stroke-width='1' 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='16' width='16' style='forced-color-adjust: auto;'%3E%3Crect x='2' y='2' height='13' width='13' rx='2' stroke='currentcolor' stroke-width='1' fill-opacity='0' /%3E%3Cpolyline points='4,8 7,12 12,5' fill='none' stroke='currentcolor' 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='16' width='16' style='forced-color-adjust: auto;'%3E%3Crect x='2' y='2' height='13' width='13' rx='2' stroke='currentcolor' stroke-width='1' fill-opacity='0' /%3E%3Cline x1='5' y1='5' x2='12' y2='12' stroke='currentcolor' stroke-width='2' /%3E%3C/svg%3E");
  }
  
  .checkbox-mixed input:focus,
  .checkbox-mixed [role="checkbox"] {
    outline: none;
  }
  
  .checkbox-mixed [role="checkbox"]:focus,
  .checkbox-mixed [role="checkbox"]:hover {
    padding: 2px;
    border: 2px solid #005a9c;
    border-radius: 5px;
    background-color: #def;
  }
  
  .checkbox-mixed label.focus,
  .checkbox-mixed label:hover {
    padding: 2px;
    border: 2px solid #005a9c;
    background-color: #def;
    border-radius: 5px;
    cursor: pointer;
  }
  
  /*
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/
*/

Copy and Paste Full Page Example