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.
How to use it
Section titled “How to use it”<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/tokens/tokens.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/components/css/checkbox/checkbox.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/components/css/label/label.css"><!-- Or everything in one file: https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/dist/lotek.css --><div class="lt-checkbox-row"><input class="lt-checkbox" type="checkbox" id="seasonal" /><label class="lt-label" for="seasonal">I accept that floors below 12 are seasonal.</label></div>
<div class="lt-checkbox-row"><input class="lt-checkbox" type="checkbox" id="vacancies" checked /><label class="lt-label" for="vacancies">Tell me when a dry floor comes free</label></div>
<div class="lt-checkbox-row"><input class="lt-checkbox" type="checkbox" id="confirm" data-state="invalid" aria-invalid="true" /><label class="lt-label" for="confirm">I confirm that I have read the lease</label></div>
<div class="lt-checkbox-row"><input class="lt-checkbox" type="checkbox" id="deposit" checked disabled /><label class="lt-label" for="deposit">The deposit is paid</label></div>import "@lo-tek/interfaces/tokens.css"import "@lo-tek/interfaces/checkbox.css"import "@lo-tek/interfaces/label.css"lt is used in two ways, and which one depends on whether you create the element or find it.
If you create the markup, you spread the attributes in. That is the route in React, Astro and anything else that renders:
import { lt } from "@lo-tek/interfaces/react"
<div className={lt.checkbox.row}> <input {...lt.checkbox()} id="seasonal" /> <label {...lt.label()} htmlFor="seasonal">I accept that floors below 12 are seasonal.</label></div>The import goes to /react here, since the block is JSX. From there the keys are called className and htmlFor. In Astro and anything else without React you import @lo-tek/interfaces and get class and for.
If the element is already there, you set them on it. That is the route when you enhance HTML a server or a template has sent:
import { lt, type FieldState } from "@lo-tek/interfaces"
const box = document.querySelector<HTMLInputElement>("#confirm")if (!box) throw new Error("element not found")
box.addEventListener("change", () => { const state: FieldState = box.checked ? "default" : "invalid"
lt.setAttributes(box, lt.checkbox({ state }))})lt.checkbox() sets type for you, so the box cannot become a text field with the checkbox’s style. lt.checkbox.row gives the class for the row around it. See Type-safe use.
// Once for the whole app, in the page template: every stylesheet, from the WebJar.head { link(rel = "stylesheet", href = LotekWebJar.CSS) }On a Kotlin or Java server, the same with kotlinx.html and lo-tek-kotlinx-html. The builder functions are in Lt, with an enum for each list of values:
import kotlinx.html.*import no.lotek.*import no.lotek.html.lt
fun FlowContent.leaseTerms() { div { lt(Lt.checkbox.row) input { lt(Lt.checkbox()); id = "seasonal" } label { lt(Lt.label()); htmlFor = "seasonal"; +"I accept that floors below 12 are seasonal." } } div { lt(Lt.checkbox.row) input { lt(Lt.checkbox()); id = "vacancies"; checked = true } label { lt(Lt.label()); htmlFor = "vacancies"; +"Tell me when a dry floor comes free" } } div { lt(Lt.checkbox.row) input { lt(Lt.checkbox(state = FieldState.INVALID)); id = "confirm" } label { lt(Lt.label()); htmlFor = "confirm"; +"I confirm that I have read the lease" } } div { lt(Lt.checkbox.row) input { lt(Lt.checkbox()); id = "deposit"; checked = true; disabled = true } label { lt(Lt.label()); htmlFor = "deposit"; +"The deposit is paid" } }}data-state | Used for |
|---|---|
| (omitted) | Normal state |
invalid | Validation has failed |
success | The 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.
Partly checked
Section titled “Partly checked”“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 extras</label></div><div class="lt-checkbox-row"> <input class="lt-checkbox extra" type="checkbox" id="extra-pump" name="extra" value="pump" checked /> <label class="lt-label" for="extra-pump">Weekly pump-out</label></div><div class="lt-checkbox-row"> <input class="lt-checkbox extra" type="checkbox" id="extra-barrier" name="extra" value="barrier" /> <label class="lt-label" for="extra-barrier">Flood barrier at the stairwell</label></div>
<script type="module"> const all = document.getElementById("all") const options = document.querySelectorAll(".extra")
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>.
Several choices that belong together
Section titled “Several choices that belong together”If the boxes belong to the same question, group them in a Fieldset. The question is then announced before each option.
<fieldset class="lt-fieldset"><legend class="lt-legend">How should we warn you when the tide rises?</legend>
<div class="lt-checkbox-row"> <input class="lt-checkbox" type="checkbox" id="notify-sms" name="notify" value="sms" checked /> <label class="lt-label" for="notify-sms">Text message</label></div>
<div class="lt-checkbox-row"> <input class="lt-checkbox" type="checkbox" id="notify-siren" name="notify" value="siren" /> <label class="lt-label" for="notify-siren">The building siren</label></div></fieldset>Identical name values give one key with several values in FormData. Read them with formData.getAll("notify").
Classes you can style
Section titled “Classes you can style”| Class | What it is |
|---|---|
.lt-checkbox | The box itself |
.lt-checkbox-row | The row that holds the box and the text side by side |
| Variable | Default |
|---|---|
--lt-checkbox-size | var(--lt-spacing-5) |
--lt-checkbox-radius | var(--lt-spacing-1) |
The tick and the dash are drawn from --lt-icon-check and --lt-icon-dash, which follow the theme. See Customising.
Accessibility
Section titled “Accessibility”
What a screen reader is given when the recipe above is used, step by step. It is checked through AT-SPI, the
interface Orca reads, in Chromium and Firefox, on every change, by check-accessibility-tree.ts, and
every change the check lists as a mutation must make it fail.
- Focus
#seasonal: focus on thecheckbox"I accept that floors below 12 are seasonal.", not checked. - Press Space: focus on the
checkbox"I accept that floors below 12 are seasonal.", checked; the change to checked sent as an event. - Focus
#confirm: focus on thecheckbox"I confirm that I have read the lease", invalid.
- Every box needs a
<label>withfor. 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. “Tell me when a dry floor comes free”, not “Vacancies”.
- 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
requiredon the box. The browser then stops the submission itself.