Checkbox
A native checkbox drawn by the theme, with an indeterminate (mixed) state.
Basic
A label wrapping the checkbox toggles it. checked is two-way.
Code
<Label>
<Checkbox checked={state.terms} />
Accept terms and conditions
</Label>Usage
import { Checkbox, CheckboxGroup } from "@elements/ui/checkbox";
<Label><Checkbox checked={form.terms} /> Accept terms and conditions</Label>
// Select all: checked when every row is, mixed when only some are.
<Checkbox checked={rows.every((r) => r.on)}
indeterminate={rows.some((r) => r.on) && !rows.every((r) => r.on)}
onCheckedChange={(on) => rows.forEach((r) => r.on = on)}
attrs={{ "aria-label": "Select all" }} />Examples
With a description
onCheckedChange fires for the user's changes, not for state set in code.
You can enable or disable notifications at any time.
Code
<div class="option">
<Checkbox checked={state.notify} attrs={{ id: "notify", "aria-describedby": "notify-hint" }}
onCheckedChange={(on) => log(on)} />
<div>
<Label htmlFor="notify">Enable notifications</Label>
<p class="hint" id="notify-hint">You can enable or disable notifications at any time.</p>
</div>
</div>Group with select all
The parent is checked when all are, mixed when some are. Clicking it checks all, then clears all, then brings back the earlier mix.
Code
// state.all = { get checked() {...every}, set checked(on) {...set each},
// get mixed() {...some but not every}, set mixed(_) {} }
// From mixed, a press checks all and remembers the mix; the press after
// clearing all brings the mix back (APG mixed-state checkbox).
<Checkbox checked={state.all.checked} indeterminate={state.all.mixed} attrs={{ id: "all" }} />
<Label htmlFor="all">Show on desktop</Label>
<Label e:for={d of state.disks}>
<Checkbox checked={d.on} name="disks" value={d.id} /> {d.label}
</Label>CheckboxGroup with a parent
The group owns a string[] value. The parent box checks all, clears all, shows mixed and brings back the last mix, with no code on the page.
Code
<CheckboxGroup value={state.apples} allValues={["a", "b", "c"]} attrs={{ "aria-labelledby": "apples" }}>
<Label attrs={{ id: "apples" }}><Checkbox parent /> Apples</Label>
<div class="nested">
<Label><Checkbox value="a" /> Fuji</Label>
<Label><Checkbox value="b" /> Gala</Label>
<Label><Checkbox value="c" /> Granny Smith</Label>
</div>
</CheckboxGroup>CheckboxGroup with a starting value
The boxes in the group show the group's starting value in the server HTML, before script runs.
Nested parent checkbox
A group inside a group: the inner parent stands for one value of the outer group, and the outer parent shows mixed while the inner group is partly checked.
Table
Row selection with a select-all header in the mixed state.
| Name | Role | ||
|---|---|---|---|
| Sarah Chen | sarah.chen@example.com | Admin | |
| Marc Rodriguez | marcus.rodriguez@example.com | User | |
| Priya Patel | priya.patel@example.com | User | |
| David Kim | david.kim@example.com | Editor |
Code
<th><Checkbox checked={state.allRows.checked} indeterminate={state.allRows.mixed}
attrs={{ "aria-label": "Select all" }} /></th>
...
<td><Checkbox checked={row.selected} attrs={{ "aria-label": `Select ${row.name}` }} /></td>Disabled and invalid
States change color, never size.
Code
<Checkbox disabled />
<Checkbox disabled checked />
<Checkbox invalid />Read only
Focusable and announced, but a click, Space or a label press leaves it as it is.
Code
<Label><Checkbox checked readOnly /> Two-factor sign in (set by your organization)</Label>Required in a form
The browser will not submit until it is checked. uncheckedValue submits a value while a box is unchecked.
Code
<form onsubmit={() => signup(state)}>
<Label><Checkbox checked={state.agree} required name="agree" /> I agree to the terms</Label>
<Label><Checkbox name="newsletter" value="yes" uncheckedValue="no" /> Send me the newsletter</Label>
<Button type="submit" variant="primary">Create account</Button>
<Button type="reset">Reset</Button>
</form>
<Label><Checkbox name="remember" checked uncheckedValue="no" attrs={{ form: "signup" }} /> Remember me</Label>Cancel a change
onCheckedChange runs before the box changes and gets details second; details.cancel() keeps it as it was.
Code
<Checkbox checked={guard.value} onCheckedChange={(checked, details) => {
if (!guard.allowed) details.cancel();
}} />Right to left
The box sits at the inline start, before its label.