Skip to content

Checkbox

In review
Live · web

Form: multi-select, with a label + description (the box is the target)

Tri-state group: the parent reflects all / none / some of its children

States: tab in, toggle with Space

UncheckedCheckedMixedDisabled offDisabled onInvalid

Sizes & standalone use

SmallMedium

Toggled 0 times. The island is live.

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform;
// this is the canonical API the published @dizyx/nockerl-react package exposes.
// NockerlCheckbox ships in the package, tri-state included (CheckedState covers 'mixed').
// See the drift note for the per-platform status.)
import { NockerlCheckbox } from '@dizyx/nockerl-react';
export function ScopePicker() {
const [scopes, setScopes] = useState({ repos: true, issues: false, prs: false });
const all = Object.values(scopes).every(Boolean);
const none = Object.values(scopes).every((v) => !v);
return (
<fieldset>
<NockerlCheckbox
label="Grant repository access"
checked={all ? true : none ? false : 'mixed'}
onChange={(on) => setScopes({ repos: on, issues: on, prs: on })}
/>
<NockerlCheckbox label="Repos" checked={scopes.repos} onChange={(v) => setScopes({ ...scopes, repos: v })} />
<NockerlCheckbox label="Issues" checked={scopes.issues} onChange={(v) => setScopes({ ...scopes, issues: v })} />
<NockerlCheckbox label="Pull requests" checked={scopes.prs} onChange={(v) => setScopes({ ...scopes, prs: v })} />
<NockerlCheckbox label="I am over 18" checked={false} invalid />
</fieldset>
);
}
PropTypeDefaultDescription
checked *CheckedStateTri-state checked value. 'mixed' renders the indeterminate dash.
onChange(next: boolean) => voidFired with the next boolean value. Ignored while disabled.
labelstringPersistent visible label (rendered beside the box, the box is its target).
descriptionstringSupporting line under the label.
sizeNockerlCheckboxSize'md'Box scale. md matches the platform default.
disabledbooleanfalseInert + clearly-seen (never invisible) state.
invalidbooleanfalseInvalid: warm error border + a "!" mark + a message. Color is never alone.
ariaLabelstringaria-label when there is no visible label (standalone / inline use).

Also accepts the native <input> attributes (e.g. onClick, disabled, id, aria-*, data-*), forwarded straight through, plus a forwarded ref.

Cross-platform drift (tracked for reconciliation)

The checkbox is the multi-selection control, a square box with a check fill, deliberately distinct from its selection-control siblings: a radio is a circle with a centered dot for mutually-exclusive single choice, and a switch is a track + thumb for an instant on/off setting. The square (barely-softened radius.track corners) is the shape that says multi-select, never the radio circle.

  • Web ships it. Checkbox is exported from @dizyx/nockerl-react (the TypeScript API above), tri-state included: CheckedState covers 'mixed'.
  • Tri-state / indeterminate is shipped only on web so far. Android has the two-state Checkbox (multi-select via a MutableSet in AskUserQuestionSheet.kt) and TriStateCheckbox is available but unused; Voice has no checkbox at all. The parent/child mixed behaviour here is designed originally. Wire it into a real Compose TriStateCheckbox and a Swift mixed-flag when those screens need it.
  • Box treatment: the web island draws a square recessed well (1:1, subtly softened radius.track corners) that lifts to a static cyan gradient with a stroke-drawn tick (sibling to the Switch track, distinct from the radio circle). Compose + SwiftUI use the stock platform box (also square) themed only on the checked color. The inset-well + catch-light treatment should flow into a custom Compose box when web’s API ships.
  • Invalid state: only web models an invalid checkbox (warm border + ! + message). Compose and SwiftUI surface validation at the form level today.