Checkbox
A box for a yes-or-no choice, with a third, mixed state for a box that stands for a list.
- Click a box or its label; Space toggles the box in focus.
- While only some events are checked, Everything shows a dash and reads as mixed.
- Click the mixed box: every event is checked. Click again to clear them all.
Field marks
- Import
import { Checkbox } from "cennge-ui";- Size
- 2.8 KB gzip for this import, its dependencies aside
- Pattern
- WAI-ARIA checkbox (Radix Checkbox): a
<button role="checkbox">named by its label - Keyboard
- Space
- State
- Controlled with
isSelectedandonValueChange, or uncontrolled withdefaultSelected - SSR
- Rendered on the server in the Next.js check; hydrates without a warning
- Mixed
isIndeterminatedraws a dash and reads asaria-checked="mixed"- Forms
- Submits
value,"on"by default, undernamewhile checked - Tests
- Unit tests; end-to-end in Chromium, Firefox and WebKit, on desktop and phone viewports: form, table
- Source
- src/components/checkbox
When to use#
A Checkbox is a choice that takes effect when the form around it is sent: agree to the terms, publish with provenance, include these labels. Several boxes in a list are independent of each other, and one more box above them can stand for the whole list.
Similar#
- Switch turns a setting on or off the moment it flips, with no form to submit.
- Radio is for one choice out of several; checkboxes allow any number.
- Table selects rows with this Checkbox;
useTableSelectiontells the header box when all or some rows are checked.
With a description#
description sits under the label in smaller, muted text and is linked to the box with aria-describedby, so it is read after the label.
import { Checkbox } from "cennge-ui";
export default function ProvenanceCheckbox() {
return (
<Checkbox defaultSelected description="npm links the package to the commit and the workflow that built it.">
Publish with provenance
</Checkbox>
);
}Some, all or none#
A parent box is checked when all its children are, mixed when some are, and empty when none are. Pass isIndeterminate for the mixed case; a click on a mixed box reports true, so the parent checks everything first.
import { useState } from "react";
import { Checkbox } from "cennge-ui";
const labels = ["bug", "documentation", "good first issue"];
export default function LabelFilter() {
const [checked, setChecked] = useState<string[]>(["bug"]);
const all = checked.length === labels.length;
return (
<div className="flex flex-col gap-3">
<Checkbox
isSelected={all}
isIndeterminate={checked.length > 0 && !all}
onValueChange={(isSelected) => setChecked(isSelected ? labels : [])}
>
All labels
</Checkbox>
{labels.map((label) => (
<Checkbox
key={label}
className="pl-7.5"
isSelected={checked.includes(label)}
onValueChange={(isSelected) =>
setChecked((current) => (isSelected ? [...current, label] : current.filter((item) => item !== label)))
}
>
{label}
</Checkbox>
))}
</div>
);
}Required, in a form#
Inside a Form, isRequired stops a submit while the box is empty. The box turns red, the browser's message shows under the label, and focus moves to the box; checking it clears the error. A checked box submits "on" under its name, or your own value.
import { useState } from "react";
import { Button, Checkbox, Form } from "cennge-ui";
export default function DeleteRepositoryForm() {
const [sent, setSent] = useState<string | null>(null);
return (
<Form
onSubmit={(event) => {
event.preventDefault();
setSent(String(new FormData(event.currentTarget).get("confirm")));
}}
className="flex w-full max-w-96 flex-col gap-4"
>
<Checkbox name="confirm" isRequired>
I understand that payload-cache and its issues will be deleted
</Checkbox>
<Button type="submit" color="danger" className="self-start">
Delete payload-cache
</Button>
<p aria-live="polite" className="text-sm text-muted">
{sent === null ? "Nothing submitted yet" : `Submitted confirm=${sent}`}
</p>
</Form>
);
}Colours and sizes#
color paints the checked and mixed box; primary follows the accent. Sizes keep the box aligned with the first line of its label.
import { Checkbox, type CenngeColor } from "cennge-ui";
const colors: CenngeColor[] = ["default", "primary", "secondary", "success", "warning", "danger", "info"];
export default function CheckboxColors() {
return (
<div className="flex flex-col gap-5">
<div className="flex flex-wrap gap-x-6 gap-y-3">
{colors.map((color) => (
<Checkbox key={color} color={color} defaultSelected>
{color[0]!.toUpperCase() + color.slice(1)}
</Checkbox>
))}
</div>
<div className="flex flex-wrap items-start gap-x-6 gap-y-3">
<Checkbox size="sm" defaultSelected>
Small
</Checkbox>
<Checkbox size="md" defaultSelected>
Medium
</Checkbox>
<Checkbox size="lg" defaultSelected>
Large
</Checkbox>
<Checkbox isDisabled defaultSelected>
Disabled
</Checkbox>
</div>
</div>
);
}Accessibility#
The label is a real <label> for the box, so a click on the text toggles it and screen readers name the box by it. Without visible text, pass aria-label. Space toggles; Enter does nothing, as the WAI-ARIA checkbox pattern says. While isInvalid, the box carries aria-invalid and the error replaces the description.
Props#
Also takes every attribute of <button>, except color, onChange, value, defaultValue, which mean something else here.