Indeterminate

Indeterminate Checked vs Unchecked, where All is a separate control, not just a status.

by _sir

HTML

<label><input type="checkbox" id="all"> <span>All: <span id="state">Unchecked</span></span></label>
<div class="nested">
  <label for=""><input type="checkbox">1</label>
  <label for=""><input type="checkbox">2</label>
  <label for=""><input type="checkbox">3</label>
  <label for=""><input type="checkbox">4</label>
  <label for=""><input type="checkbox">5</label>  
</div>
<div class="explainer">
<p>
 The "All" checkbox is a separate control and not just a convenience mechanism for activating all the checkboxes. When pagination is available we need "All" to keep track of state independenty from the rows.
</p>
<p>
Because "All" keeps track of whether it is in an "all checked" or "all unchecked" state in addition to displaying indeterminate state, you can still click it from the indeterminate state and have the checkboxes transition either way.
</p>
<p>
"All" represents information that may not be on screen, which can be confusing. It may remain indeterminate while all checkboxes on the current page are the same. In our code, it is easy to tell if "All" is indeterminate <i>once a user has interacted with it</i>. If the column data contains members, it is most likely indeterminate.
</p>
<p>
 There are rare cases where changing a row would make all entries match and "satisfy" an All Checked condition, but we cannot be certain of that if pagination exists, so we do not adjust the checked state of "All" unless a user interacts with it.
</p>
</div>

CSS

body {
  font-family: sans-serif;
}
label {
  display: block;
}

.nested {
  margin-left: 1em;
}

.explainer {
  width: 35em;
}

JavaScript

const all = document.getElementById('all');
const allState = document.getElementById('state');
const boxes = [...document.querySelectorAll('.nested input')];

const checkedState = () => {
  const all = boxes.every(e => e.checked);
  const some = boxes.some(e => e.checked);
  return (all * 0.5) + (some * 0.5);
};

const reportAllState = () => {
  console.log('all', all.checked, all.indeterminate);
  allState.innerText = `${all.checked ? 'Checked' : 'Unchecked'}${all.indeterminate ? ', Indeterminate' : ''}`;
}

all.addEventListener('change', () => {
  const newState = all.checked;
  boxes.forEach(e => e.checked = newState);
  reportAllState();
});

boxes.forEach((box) => {
  box.addEventListener('change', () => {
    const newAll = checkedState();
    const matching = !!Math.floor(newAll) === all.checked;
    /*     if (newAll !== 0.5) {
          all.checked = !!newAll;
        } */
    all.indeterminate = (newAll === 0.5 ? true : !matching);
    reportAllState();
  })
})