Checking and unchecking a checkbox with JavaScript: a quick lesson on HTML IDL attributes

I feel like an idiot for this amount of time I spent on this one today at work.

TL:DR; What you're looking for is:

// <input type="checkbox" id="your-checkbox-id" />
const yourCheckbox = document.getElementById('your-checkbox-id');

// check the checkbox
yourCheckbox.checked = true;

// uncheck the checkbox
yourCheckbox.checked = false;

So what was I doing wrong today? Let's take a look at the Codepen below.

See the Pen WIP: by Aileen Rae (@aileen-r) on CodePen.

❌ Using the setAttribute method

I had a list of checkboxes, with one "select all" checkbox at the top. When the "select all" checkbox is checked, I wanted to check all the underlying checkboxes.

I tried setting the checked attribute on the input elements. My code looked something like this:

const selectAllCheckbox = document.getElementById('select-all');
const childCheckboxes = document.querySelectorAll('.child-checkbox');

const toggleSelectAllCheckbox = () => {
  const areAllChecked = [...childCheckboxes].every(c => c.hasAttribute('checked'));
  if (areAllChecked) {
    // uncheck all if every checkbox is checked
    childCheckboxes.forEach(c => {
      c.removeAttribute('checked');
    })
  }
  else {
    // otherwise, check all
    childCheckboxes.forEach(c => {
      c.setAttribute('checked', '');
    })
  }
}

selectAllCheckbox.addEventListener('click', toggleSelectAllCheckbox);

This seems alright at first glance. If you toggle the "select all" checkbox it works.

However, if you toggle any of the child checkboxes then toggle "select all", it ignores the children you just toggled. Try it out in the Codepen example above. This won't do.

✅ Setting the "checked" property

Eventually, I figured out using the setAttribute() and removeAttribute() methods was where I was going wrong. Instead, I needed to access and set the checked property directly, like so:

const selectAllCheckbox = document.getElementById('select-all');
const childCheckboxes = document.querySelectorAll('.child-checkbox');

const toggleSelectAllCheckbox = () => {
  const areAllChecked = [...childCheckboxes].every(c => c.checked === true)
  childCheckboxes.forEach(c => {
    c.checked = !areAllChecked;
  })
}

selectAllCheckbox.addEventListener('click', toggleSelectAllCheckbox);

There is some extra work to make sure the "select all" checkbox updates when toggling the children, but you can check out the Codepen for that.

Why was I wrong?

When it comes to setting attributes on HTML elements, I had always assumed element.setAttribute('attribute', value) was roughly interchangeable with element.attribute = value. But there is a subtle difference.

HTML attributes have two sides, the content attribute and the IDL attribute.

The content attribute is what is written in HTML. It is also accessed via the methods element.hasAttribute(), element.getAttribute(), element.setAttribute(), and element.removeAttribute().

For example, if I have <input type="checkbox" id="your-checkbox-id" />, checkboxElement.hasAttribute('checked') will be false.

If I then set checkboxElement.setAttribute('checked') and use a devtools inspector, my HTML will now be <input type="checkbox" id="your-checkbox-id" checked />.

Screenshot of a checkbox element in Firefox devtools inspector.

The IDL attribute is a JavaScript property. If I log my element to the console with console.log('checkboxElement'), I can drill into it like any JavaScript object and see all the properites. These are the IDL attributes.

Screenshot of a checkbox element logged to the Firefox developer console. A list of properties, including "checked" are visible.

The IDL attribute will use the underlying content attribute. element.setAttribute("attribute", value) and element.attribute = value are interchangable...most of the time.

The catch with checkboxes is that, for the checked IDL attribute, the underlying content attribute is not checked. It doesn't have one.

The content attribute checked for inputs with type="checkbox" instead corresponds to the defaultChecked IDL attribute. Even though element.setAttribute("checked") sort of works for programatically checking a checkbox (at least in the browsers I tried), it's not supposed to.

Use the checked content attribute if you want the checkbox checked by default at page load (or when the element is first painted).

If you want to mutate the checkbox with JavaScript, use the checked IDL attribute: element.checked = true.

Sources

MDN: <input type="checkbox">, IDL glossary entry.