Checkbox
stimeo--checkbox
Native checkboxes with tri-state parent/child 'select all' linkage and a change event.
The stimeo--checkbox controller adds what HTML cannot express to native <input type="checkbox">: the parent's indeterminate property (announced as mixed) derived from its children, and the cascade from a parent toggle to every child. The aggregate (all / partial / none) is mirrored to data-state on the root, and stimeo--checkbox:change is dispatched on every change. The check mark and any mixed affordance are owned by this Playground.
1 selected
Keyboard
| Key | Action |
|---|---|
| Space | Toggle the focused checkbox (native). |
<%# Markup for the checkbox (APG Checkbox / 3-state) demo.
The parent ("select all") derives checked / indeterminate from the children's
state, and a parent toggle sets all children at once. The aggregate state is
reflected on the root's data-state (all/partial/none); the look is the consumer's CSS. %>
<div class="checkbox-demo" role="group" aria-labelledby="cb-group-label"
data-controller="stimeo--checkbox">
<span class="checkbox-demo__legend" id="cb-group-label">
<%= t("components.checkbox.demo.group") %>
</span>
<label class="checkbox-demo__row checkbox-demo__row--parent">
<input type="checkbox" data-stimeo--checkbox-target="parent"
data-action="change->stimeo--checkbox#onParentChange" />
<span><%= t("components.checkbox.demo.all") %></span>
</label>
<div class="checkbox-demo__children">
<label class="checkbox-demo__row">
<input type="checkbox" checked data-stimeo--checkbox-target="child"
data-action="change->stimeo--checkbox#onChildChange" />
<span><%= t("components.checkbox.demo.item_a") %></span>
</label>
<label class="checkbox-demo__row">
<input type="checkbox" data-stimeo--checkbox-target="child"
data-action="change->stimeo--checkbox#onChildChange" />
<span><%= t("components.checkbox.demo.item_b") %></span>
</label>
<label class="checkbox-demo__row">
<input type="checkbox" data-stimeo--checkbox-target="child"
data-action="change->stimeo--checkbox#onChildChange" />
<span><%= t("components.checkbox.demo.item_c") %></span>
</label>
</div>
<%# Subscribe to the change event and show the selected count (updated by demo.js). %>
<p class="checkbox-demo__status" aria-live="polite">
<span data-checkbox-count>1</span> <%= t("components.checkbox.demo.selected") %>
</p>
</div>
/*
* Presentation-only styles for the checkbox demo.
* The aggregate state is available on the root's data-state (all/partial/none).
*/
.checkbox-demo {
display: flex;
flex-direction: column;
gap: 0.4rem;
padding: 0;
border: 0;
max-width: 22rem;
}
.checkbox-demo__legend {
font-weight: 600;
margin-bottom: 0.25rem;
}
.checkbox-demo__row {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.checkbox-demo__row input {
width: 1.1rem;
height: 1.1rem;
accent-color: var(--accent);
}
.checkbox-demo__row--parent {
font-weight: 600;
padding-bottom: 0.4rem;
border-bottom: 1px solid var(--border);
}
.checkbox-demo__children {
display: flex;
flex-direction: column;
gap: 0.35rem;
padding-left: 1.25rem;
}
.checkbox-demo__status {
margin: 0.5rem 0 0;
font-size: 0.85rem;
color: var(--color-text-muted);
}
// Demo script: subscribe to the checkbox change event and show how many children are checked.
document.addEventListener('stimeo--checkbox:change', function () {
const checked = document.querySelectorAll(
'.checkbox-demo [data-stimeo--checkbox-target="child"]:checked',
).length;
const out = document.querySelector('[data-checkbox-count]');
if (out) out.textContent = String(checked);
});
These demo styles use shared design tokens (light + dark). Copy the shared styles too, then toggle data-theme on your root element for dark mode.
The data-* attributes you add to your own HTML to wire this component. Put the data-controller below on a root element, then place its targets / values / actions inside that element.
On the root element
data-controller="stimeo--checkbox"
Targets
| Name | Description | Attribute |
|---|---|---|
parent
required
|
The "select all" checkbox whose checked/indeterminate state is derived from the children. | data-stimeo--checkbox-target="parent" |
child
|
An individual checkbox in the group, counted to compute the parent's aggregate state. | data-stimeo--checkbox-target="child" |
Actions
| Name | Description | Action |
|---|---|---|
onChildChange
|
Recomputes the parent's checked/indeterminate from the children on a child change. | stimeo--checkbox#onChildChange |
onParentChange
|
Cascades the parent's checked state to every child and clears its indeterminate. | stimeo--checkbox#onParentChange |
Events
| Name | Description | Event |
|---|---|---|
change
|
Fires on every state change; detail { checked, indeterminate, state } where state is all/partial/none. |
stimeo--checkbox:change |
State hooks
The library only manages these ARIA/data attributes and custom properties. Your CSS reads them to render the look — selectors like [aria-selected], [aria-expanded], or var(--stimeo--…) hook into this state.
| Hook | Target | Meaning |
|---|---|---|
indeterminate |
Parent | true when only some children are checked (announced as "mixed"). |
data-state |
Root | "all" / "partial" / "none" — the aggregate selection state. |