Skip to content

Checkbox

Plain CSS on an ordinary <input type="checkbox">, so the keyboard, FormData and the browser’s own validation work as before. If the user should choose one of several options, Radio is right. If the choice turns a setting on or off and takes effect at once, use Switch.

data-stateUsed for
(omitted)Normal state
invalidValidation has failed
successThe choice is confirmed valid. Use sparingly.

data-state is colour. Screen readers also need aria-invalid="true". lt.checkbox({ state: "invalid" }) and Field both set it.

“Select all” is neither on nor off when some of the options under it are checked. That state exists only as a property in JavaScript, not as an HTML attribute:

<div class="lt-checkbox-row">
<input class="lt-checkbox" type="checkbox" id="all" />
<label class="lt-label" for="all">Select all attachments</label>
</div>
<div class="lt-checkbox-row">
<input class="lt-checkbox attachment" type="checkbox" id="attachment-certificate" name="attachment" value="certificate" checked />
<label class="lt-label" for="attachment-certificate">Birth certificate</label>
</div>
<div class="lt-checkbox-row">
<input class="lt-checkbox attachment" type="checkbox" id="attachment-lease" name="attachment" value="lease" />
<label class="lt-label" for="attachment-lease">Tenancy agreement</label>
</div>
<script type="module">
const all = document.getElementById("all")
const options = document.querySelectorAll(".attachment")
function update() {
const count = [...options].filter((box) => box.checked).length
all.checked = count === options.length
all.indeterminate = count > 0 && count < options.length
}
// "Select all" checks or unchecks everything under it.
all.addEventListener("change", () => {
for (const box of options) box.checked = all.checked
update()
})
for (const box of options) box.addEventListener("change", update)
update()
</script>

Screen readers announce the state as “partially checked” by themselves. You do not need aria-checked="mixed" on a real <input>.

If the boxes belong to the same question, group them in a Fieldset. The question is then announced before each option.

Identical name values give one key with several values in FormData. Read them with formData.getAll("notify").

ClassWhat it is
.lt-checkboxThe box itself
.lt-checkbox-rowThe row that holds the box and the text side by side
VariableDefault
--lt-checkbox-sizevar(--lt-spacing-5)
--lt-checkbox-radiusvar(--lt-spacing-1)

The tick and the dash are drawn from --lt-icon-check and --lt-icon-dash, which follow the theme. See Customising.

  • Every box needs a <label> with for. Without it the click area is only the box itself, and the screen reader reads nothing.
  • The text should say what happens when the box is checked, not what the question was called. “Send me newsletters”, not “Newsletter”.
  • Do not submit the form or navigate when a box changes. The user expects to be able to check several before anything happens.
  • If checking is required, set required on the box. The browser then stops the submission itself.