Keyboard
| Key | Effect |
|---|---|
| Space | Toggles checked / unchecked. |
| Tab | Move focus. |
Pure state machines (FSMs). No DOM. Run in server, Vitest, Worker. Reach for these when you only want the behavior.
Tri-state (unchecked / checked / mixed) checkbox.
pnpm add @kumiki/machinesimport { createCheckboxMachine } from '@kumiki/machines/checkbox';
const m = createCheckboxMachine({ initial: 'mixed' });
m.send({ type: 'TOGGLE' }); // mixed → checked (APG tristate)
console.log(m.context.value); // 'checked'
m.send({ type: 'SET', value: 'mixed' });
console.log(m.state); // 'mixed'Svelte 5 {@attach} factories. Glue ARIA / keyboard / focus onto any DOM node you choose. **Best when you want full control over markup and styling.**
pnpm add @kumiki/headless<script lang="ts">
import { createCheckbox } from '@kumiki/headless/checkbox';
const c = createCheckbox({ initial: 'unchecked' });
</script>
<button {@attach c.root}>
{c.value === 'checked' ? '✓' : c.value === 'mixed' ? '−' : ''}
</button>Compound components (<Root> / <Trigger> / …). Markup is fixed; styling is not. Same trade-off as a typical headless UI library.
pnpm add @kumiki/components/checkbox<script lang="ts">
import { Checkbox } from '@kumiki/components/checkbox';
let value = $state<'unchecked' | 'checked' | 'mixed'>('unchecked');
</script>
<Checkbox.Root bind:value>
{value === 'checked' ? '✓' : value === 'mixed' ? '−' : ''}
</Checkbox.Root><script lang="ts">
import { Checkbox } from '@kumiki/components/checkbox';
let items = $state([
{ id: 1, checked: false },
{ id: 2, checked: true },
{ id: 3, checked: false },
]);
// mixed when some-but-not-all children checked
const parentValue = $derived(
items.every((i) => i.checked)
? 'checked'
: items.some((i) => i.checked)
? 'mixed'
: 'unchecked',
);
</script>
<Checkbox.Root
value={parentValue}
onCheckedChange={(v) => items.forEach((i) => (i.checked = v === 'checked'))}>
Select all
</Checkbox.Root>Styled, copy-paste presets (preview). Run pnpm kumiki add to drop the source into your project, then edit freely.
Live preview…
pnpm add @kumiki/atelier<script lang="ts">
import { Tailwind as Checkbox } from '@kumiki/atelier/checkbox';
import type { CheckboxValue } from '@kumiki/components/checkbox';
let agreed = $state<CheckboxValue>('unchecked');
</script>
<label>
<Checkbox.Root id="agree" bind:value={agreed} />
<span>I agree to the terms</span>
</label>/ accessibility
axe-core — run on every PR (LTR + RTL × every documented state).
| Key | Effect |
|---|---|
| Space | Toggles checked / unchecked. |
| Tab | Move focus. |