Deselectable Radio Buttons

I did this with jquery a while back and decided to update it to modern DOM. Allows a set of radio buttons to be deselectable in case such a thing is wanted.

by Travis Almand

HTML

<p>Radio buttons that can be deselected.</p>

<p>divs</p>
<div>
  <input data-deselectable type="radio" id="radio1" name="div-list" />
  <label for="radio1">radio 1</label>
</div>
<div>
  <input data-deselectable type="radio" id="radio2" name="div-list" />
  <label for="radio2">radio 2</label>
</div>

<p>fieldsets</p>
<fieldset>
  <legend>radios</legend>
  <input data-deselectable type="radio" id="radio3" name="fieldset-list" />
  <label for="radio3">radio 3</label>
  <input data-deselectable type="radio" id="radio4" name="fieldset-list" />
  <label for="radio4">radio 4</label>
</fieldset>

<p>multiple labels</p>
<div>
  <label for="radio5">radio 5</label>
  <input data-deselectable type="radio" id="radio5" name="multi-list" />
  <label for="radio5">radio 5</label>
</div>
<div>
  <label for="radio6">radio 6</label>
  <input data-deselectable type="radio" id="radio6" name="multi-list" />
  <label for="radio6">radio 6</label>
  <label for="radio6">radio 6</label>
</div>

<p>required</p>
<div>
  <input required data-deselectable type="radio" id="radio7" name="required-list" />
  <label for="radio7">radio 7</label>
</div>

<p>not included</p>
<div>
  <input type="radio" id="radio8" name="not-included-list" />
  <label for="radio8">radio 8</label>
</div>
<div>
  <input type="radio" id="radio9" name="not-included-list" />
  <label for="radio9">radio 9</label>
</div>

JavaScript

(function () {
  let $deselectableInputs = document.querySelectorAll('input[data-deselectable]');
  let deselectableLabels = [];

  for (let i = 0; i < $deselectableInputs.length; i++) {
    // inputs can have multiple labels
    // collect them individually to apply their own click events later
    let $labels = document.querySelectorAll('label[for="' + $deselectableInputs[i].id + '"]');
    for (let j = 0; j < $labels.length; j++) {
      deselectableLabels.push($labels[j]);
    }

    // mouseup event because "click" doesn't work
    $deselectableInputs[i].addEventListener('mouseup', function (e) {
        if (e.target.checked) {
          // delay introduced otherwise false checked doesn't take
          window.setTimeout(function () {
            e.target.checked = false;
          }, 1);
        }
    });
  }

  deselectableLabels.forEach(function (item, index) {
    // in this case the click event does work
    deselectableLabels[index].addEventListener('click', function (e) {
      // get relevant input
      const $input = e.target.control;
      
      if ($input.checked) {
        // still need delay
        window.setTimeout(function () {
          $input.checked = false;
        }, 1);
      }
    });
  });
})();