Checkbox
Native checkbox with controllable checked state, optional indeterminate (ARIA mixed), and the shared Sometic styling / state-attribute contract across React, Vue, custom elements, and the DOM engine.
Usage
import { useState } from "react";
import { Checkbox } from "@sometic/react/selection";
export function Example() {
const [checked, setChecked] = useState(false);
return <Checkbox checked={checked} onCheckedChange={setChecked} />;
}import { useState } from "react";
import { Checkbox } from "@sometic/react/selection";
export function Example(): JSX.Element {
const [checked, setChecked] = useState(false);
return <Checkbox checked={checked} onCheckedChange={setChecked} />;
}<script type="module">
import { registerSelectionElements } from "@sometic/elements/selection";
registerSelectionElements();
</script>
<sometic-checkbox></sometic-checkbox>How it works
- Engine (
@sometic/dom/checkbox):resolveCheckboxbuilds attributes (aria-checked,data-checked,data-indeterminate, …) and native flags.createCheckboxControllerowns controllablecheckedplus mutableindeterminate;toggle/setCheckedclear indeterminate.bindCheckboxsyncs a live input and callsonCheckedChange. - Adapters: React/Vue render
<input type="checkbox">, resolve each render, and bridge controlled/uncontrolled checked state. - Custom element:
sometic-checkboxobserves attributes, hosts a light-DOM checkbox, and emitschecked-change.
Anatomy
| Part | data-slot | Role |
|---|---|---|
| Root | root | Native <input type="checkbox"> (or CE host attrs + inner input) |
State / ARIA from resolve:
data-checked:"true"|"indeterminate"(when mixed)data-indeterminate,data-disabled,data-invalid- optional
data-size/data-variant aria-checked:"true"|"false"|"mixed"aria-invalidwheninvalid
Props / attributes
React CheckboxProps
Omits native type / checked / defaultChecked / onChange; extends ResolveCheckboxOptions and remaining input HTML attributes.
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | , | Controlled checked |
defaultChecked | boolean | false | Uncontrolled initial |
onCheckedChange | (checked: boolean) => void | , | Change callback |
indeterminate | boolean | false | Visual / ARIA mixed |
disabled | boolean | false | Disables interaction |
required | boolean | false | Native required |
invalid | boolean | false | Invalid + aria-invalid |
name | string | , | Form name |
value | string | , | Form value when checked |
size / variant | string | , | Theme state attrs |
unstyled / classes / styles / cssVariables / defaults / variants / merge | styling | , | Styleable contract (root slot) |
| Native attrs | remaining input attrs | , | Forwarded; ref supported |
Custom element (sometic-checkbox)
Observed: checked, indeterminate, disabled, name, value, shadow.
Vue
v-model:checked → checked / update:checked; also emits checkedChange.
Events / callbacks
| Surface | Event | Payload |
|---|---|---|
| React | onCheckedChange | boolean |
| Vue | update:checked, checkedChange | boolean |
| Custom element | checked-change | { checked: boolean } |
bindCheckbox | native change | element.checked |
Controlled vs uncontrolled
- Controlled: pass
checked(+onCheckedChange). - Uncontrolled: omit
checked, usedefaultChecked. indeterminateis always a prop/attribute overlay; toggling checked on controller/CE clears it.
Form participation
Native checkbox: when checked, name/value appear in form submit. Indeterminate is not a third submit state, posting follows checked/unchecked only. Works inside native forms, Form, and sometic-form.
Accessibility
- Prefer a visible
<label>oraria-label. - Indeterminate ⇒
aria-checked="mixed"anddata-checked="indeterminate". - Keep keyboard activation on the native input; do not replace with a div role.
- React resolves ARIA/
data-*from theindeterminateprop; set the DOMindeterminateproperty yourself if you need the browser’s native paint beyond attributes.
Styling
Unstyled by default. Target:
[data-checked="true"],[data-checked="indeterminate"][data-indeterminate="true"][data-disabled],[data-invalid][data-slot="root"]
Light DOM is the CE default. shadow opts into an open shadow root.
Edge cases
- Indeterminate + submit, visual only; FormData still uses checked.
- User toggle clears indeterminate, on controller and CE paths.
- Controlled without handler: UI will not update when clicked (expected controlled contract).
- SSR, resolve is pure; register CE in the browser.
- Multi-instance, no shared singleton state.
Performance notes
Resolve is pure; adapters avoid controllers unless you opt into createCheckboxController for vanilla orchestration. Thin adapters keep React/Vue bundles small, import @sometic/react/selection rather than inventing parallel checkbox logic.
When to use / When not
Use for independent on/off choices, multi-select boolean lists, and tri-state “select all” via indeterminate.
Do not use for mutually exclusive options (Radio), instant settings switch UX (Switch), or toolbar pressed state (Toggle button).
FAQ
Does indeterminate count as checked for submit? No. Visual/ARIA only.
Does user toggle clear indeterminate? Yes on controller and custom-element paths.
Controlled vs uncontrolled? Pass checked for controlled; omit and use defaultChecked for uncontrolled.
Why not a pure ARIA checkbox? Native HTML keeps form serialization and platform semantics intact.
React and the DOM indeterminate property? Resolve sets ARIA/data-*; set element.indeterminate yourself if you need native paint.
Switch vs Checkbox? Same input type under the hood for Switch, but Switch resolve uses role="switch" semantics, pick the control that matches UX expectations.
Invalid without forms? invalid still sets attributes for styling/ARIA; pair with Field or forms meta for messages.
CE shadow? Isolates styles; form participation still uses the inner input in light or shadow mount root.