Docs Components Forms

Checkbox

React example follows below.
ts
import { Checkbox } from "@grassroot/ui-react";
import * as React from "react";
const checkboxFixture = {
id: "checkbox",
options: [
{ id: "new-build", label: "New build", checked: true },
{ id: "rescue-job", label: "Rescue job", checked: false },
{ id: "unavailable-option", label: "Unavailable option", checked: false, disabled: true },
],
/**
* The parent/child group. The parent is never stored — it is DERIVED from
* its children on every render: all checked, none checked, or the third
* state in between. One child starts checked so the docs land on that third
* state rather than needing a click to reach it.
*/
group: {
parentLabel: "All regions",
children: [
{ id: "north", label: "North", checked: true },
{ id: "midlands", label: "Midlands", checked: false },
{ id: "south", label: "South", checked: false },
],
},
} satisfies {
id: string;
options: Array<{ id: string; label: string; checked: boolean; disabled?: boolean }>;
group: {
parentLabel: string;
children: Array<{ id: string; label: string; checked: boolean }>;
};
};
/**
* The parent's state, derived from its children. Shared by all five demos so
* the rule itself is written once and cannot drift between frameworks.
*
* Clicking a parent that is in the third state CHECKS every child rather than
* clearing them — the convention every checklist UI follows, and the one that
* cannot lose a selection the reader already made by accident.
*/
function checkboxGroupParentState(
children: Readonly<Record<string, boolean>>,
): boolean | "indeterminate" {
const values = Object.values(children);
if (values.every(Boolean)) return true;
if (values.some(Boolean)) return "indeterminate";
return false;
}
const { group } = checkboxFixture;
export default function CheckboxDemo() {
const [checked, setChecked] = React.useState(() =>
Object.fromEntries(checkboxFixture.options.map((option) => [option.id, option.checked])),
);
const [children, setChildren] = React.useState(() =>
Object.fromEntries(group.children.map((child) => [child.id, child.checked])),
);
// Never stored — the parent has no state of its own to fall out of sync.
const parent = checkboxGroupParentState(children);
return (
<div className="flex flex-col gap-3.5">
{checkboxFixture.options.map((option) => (
<Checkbox
key={option.id}
label={option.label}
checked={checked[option.id]}
disabled={option.disabled}
onChange={(event) =>
setChecked((current) => ({ ...current, [option.id]: event.target.checked }))
}
/>
))}
<Checkbox
label={group.parentLabel}
checked={parent}
onChange={(event) =>
setChildren(
Object.fromEntries(group.children.map((child) => [child.id, event.target.checked])),
)
}
/>
{/* Inline, not `pl-[30px]`: the docs app ships ui-core's precompiled
utilities and runs no Tailwind build, so an arbitrary class authored
here emits no rule. */}
<div className="flex flex-col gap-3.5" style={{ paddingLeft: 30 }}>
{group.children.map((child) => (
<Checkbox
key={child.id}
label={child.label}
checked={children[child.id]}
onChange={(event) =>
setChildren((current) => ({ ...current, [child.id]: event.target.checked }))
}
/>
))}
</div>
</div>
);
}
ts
<script setup lang="ts">
import { computed, ref } from "vue";
import { Checkbox } from "@grassroot/ui-vue";
const checkboxFixture = {
id: "checkbox",
options: [
{ id: "new-build", label: "New build", checked: true },
{ id: "rescue-job", label: "Rescue job", checked: false },
{ id: "unavailable-option", label: "Unavailable option", checked: false, disabled: true },
],
/**
* The parent/child group. The parent is never stored — it is DERIVED from
* its children on every render: all checked, none checked, or the third
* state in between. One child starts checked so the docs land on that third
* state rather than needing a click to reach it.
*/
group: {
parentLabel: "All regions",
children: [
{ id: "north", label: "North", checked: true },
{ id: "midlands", label: "Midlands", checked: false },
{ id: "south", label: "South", checked: false },
],
},
} satisfies {
id: string;
options: Array<{ id: string; label: string; checked: boolean; disabled?: boolean }>;
group: {
parentLabel: string;
children: Array<{ id: string; label: string; checked: boolean }>;
};
};
/**
* The parent's state, derived from its children. Shared by all five demos so
* the rule itself is written once and cannot drift between frameworks.
*
* Clicking a parent that is in the third state CHECKS every child rather than
* clearing them — the convention every checklist UI follows, and the one that
* cannot lose a selection the reader already made by accident.
*/
function checkboxGroupParentState(
children: Readonly<Record<string, boolean>>,
): boolean | "indeterminate" {
const values = Object.values(children);
if (values.every(Boolean)) return true;
if (values.some(Boolean)) return "indeterminate";
return false;
}
const group = checkboxFixture.group;
const checked = ref(
Object.fromEntries(checkboxFixture.options.map((option) => [option.id, option.checked])),
);
const children = ref(
Object.fromEntries(group.children.map((child) => [child.id, child.checked])),
);
// Never stored — the parent has no state of its own to fall out of sync.
const parent = computed(() => checkboxGroupParentState(children.value));
function onChange(id: string, event: Event) {
checked.value[id] = (event.target as HTMLInputElement).checked;
}
function onChildChange(id: string, event: Event) {
children.value[id] = (event.target as HTMLInputElement).checked;
}
function onParentChange(event: Event) {
const next = (event.target as HTMLInputElement).checked;
children.value = Object.fromEntries(group.children.map((child) => [child.id, next]));
}
</script>
<template>
<div class="flex flex-col gap-3.5">
<Checkbox
v-for="option in checkboxFixture.options"
:key="option.id"
:checked="checked[option.id]"
:disabled="option.disabled"
@change="onChange(option.id, $event)"
>
<template #label>{{ option.label }}</template>
</Checkbox>
<Checkbox :checked="parent" @change="onParentChange($event)">
<template #label>{{ group.parentLabel }}</template>
</Checkbox>
<!-- Inline, not `pl-[30px]`: the docs app ships ui-core's precompiled
utilities and runs no Tailwind build, so an arbitrary class authored
here emits no rule. -->
<div class="flex flex-col gap-3.5" style="padding-left: 30px">
<Checkbox
v-for="child in group.children"
:key="child.id"
:checked="children[child.id]"
@change="onChildChange(child.id, $event)"
>
<template #label>{{ child.label }}</template>
</Checkbox>
</div>
</div>
</template>
ts
import { Component, computed, signal } from "@angular/core";
import { GrassrootCheckbox } from "@grassroot/ui-angular";
const checkboxFixture = {
id: "checkbox",
options: [
{ id: "new-build", label: "New build", checked: true },
{ id: "rescue-job", label: "Rescue job", checked: false },
{ id: "unavailable-option", label: "Unavailable option", checked: false, disabled: true },
],
/**
* The parent/child group. The parent is never stored — it is DERIVED from
* its children on every render: all checked, none checked, or the third
* state in between. One child starts checked so the docs land on that third
* state rather than needing a click to reach it.
*/
group: {
parentLabel: "All regions",
children: [
{ id: "north", label: "North", checked: true },
{ id: "midlands", label: "Midlands", checked: false },
{ id: "south", label: "South", checked: false },
],
},
} satisfies {
id: string;
options: Array<{ id: string; label: string; checked: boolean; disabled?: boolean }>;
group: {
parentLabel: string;
children: Array<{ id: string; label: string; checked: boolean }>;
};
};
/**
* The parent's state, derived from its children. Shared by all five demos so
* the rule itself is written once and cannot drift between frameworks.
*
* Clicking a parent that is in the third state CHECKS every child rather than
* clearing them — the convention every checklist UI follows, and the one that
* cannot lose a selection the reader already made by accident.
*/
function checkboxGroupParentState(
children: Readonly<Record<string, boolean>>,
): boolean | "indeterminate" {
const values = Object.values(children);
if (values.every(Boolean)) return true;
if (values.some(Boolean)) return "indeterminate";
return false;
}
@Component({
selector: "app-checkbox-demo",
standalone: true,
imports: [GrassrootCheckbox],
host: { style: "display: contents" },
template: `
<div class="flex flex-col gap-3.5">
@for (option of fixture.options; track option.id) {
<grassroot-checkbox
[checked]="checked()[option.id]"
[disabled]="option.disabled ?? false"
[label]="option.label"
(checkedChange)="setChecked(option.id, $event.checked)"
/>
}
<grassroot-checkbox
[checked]="parent()"
[label]="fixture.group.parentLabel"
(checkedChange)="setAll($event.checked)"
/>
<!-- Inline, not 'pl-[30px]': the docs app ships ui-core's precompiled
utilities and runs no Tailwind build, so an arbitrary class authored
here emits no rule. -->
<div class="flex flex-col gap-3.5" style="padding-left: 30px">
@for (child of fixture.group.children; track child.id) {
<grassroot-checkbox
[checked]="children()[child.id]"
[label]="child.label"
(checkedChange)="setChild(child.id, $event.checked)"
/>
}
</div>
</div>
`,
})
export class CheckboxDemoComponent {
protected readonly fixture = checkboxFixture;
protected readonly checked = signal(
Object.fromEntries(checkboxFixture.options.map((option) => [option.id, option.checked])),
);
protected readonly children = signal(
Object.fromEntries(checkboxFixture.group.children.map((child) => [child.id, child.checked])),
);
/** Never stored — the parent has no state of its own to fall out of sync. */
protected readonly parent = computed(() => checkboxGroupParentState(this.children()));
protected setChecked(id: string, checked: boolean): void {
this.checked.update((current) => ({ ...current, [id]: checked }));
}
protected setChild(id: string, checked: boolean): void {
this.children.update((current) => ({ ...current, [id]: checked }));
}
protected setAll(checked: boolean): void {
this.children.set(
Object.fromEntries(checkboxFixture.group.children.map((child) => [child.id, checked])),
);
}
}
export default CheckboxDemoComponent;
ts
<script lang="ts">
import { Checkbox } from "@grassroot/ui-svelte";
const checkboxFixture = {
id: "checkbox",
options: [
{ id: "new-build", label: "New build", checked: true },
{ id: "rescue-job", label: "Rescue job", checked: false },
{ id: "unavailable-option", label: "Unavailable option", checked: false, disabled: true },
],
/**
* The parent/child group. The parent is never stored — it is DERIVED from
* its children on every render: all checked, none checked, or the third
* state in between. One child starts checked so the docs land on that third
* state rather than needing a click to reach it.
*/
group: {
parentLabel: "All regions",
children: [
{ id: "north", label: "North", checked: true },
{ id: "midlands", label: "Midlands", checked: false },
{ id: "south", label: "South", checked: false },
],
},
} satisfies {
id: string;
options: Array<{ id: string; label: string; checked: boolean; disabled?: boolean }>;
group: {
parentLabel: string;
children: Array<{ id: string; label: string; checked: boolean }>;
};
};
/**
* The parent's state, derived from its children. Shared by all five demos so
* the rule itself is written once and cannot drift between frameworks.
*
* Clicking a parent that is in the third state CHECKS every child rather than
* clearing them — the convention every checklist UI follows, and the one that
* cannot lose a selection the reader already made by accident.
*/
function checkboxGroupParentState(
children: Readonly<Record<string, boolean>>,
): boolean | "indeterminate" {
const values = Object.values(children);
if (values.every(Boolean)) return true;
if (values.some(Boolean)) return "indeterminate";
return false;
}
const group = checkboxFixture.group;
let checked = $state(
Object.fromEntries(checkboxFixture.options.map((option) => [option.id, option.checked])),
);
let children = $state(
Object.fromEntries(group.children.map((child) => [child.id, child.checked])),
);
// Never stored — the parent has no state of its own to fall out of sync.
let parent = $derived(checkboxGroupParentState(children));
function onChange(id: string, event: Event) {
checked[id] = (event.target as HTMLInputElement).checked;
}
function onChildChange(id: string, event: Event) {
children[id] = (event.target as HTMLInputElement).checked;
}
function onParentChange(event: Event) {
const next = (event.target as HTMLInputElement).checked;
children = Object.fromEntries(group.children.map((child) => [child.id, next]));
}
</script>
<div class="flex flex-col gap-3.5">
{#each checkboxFixture.options as option (option.id)}
<Checkbox
checked={checked[option.id]}
label={option.label}
disabled={option.disabled}
onChange={(event) => onChange(option.id, event)}
/>
{/each}
<Checkbox checked={parent} label={group.parentLabel} onChange={onParentChange} />
<!-- Inline, not `pl-[30px]`: the docs app ships ui-core's precompiled
utilities and runs no Tailwind build, so an arbitrary class authored
here emits no rule. -->
<div class="flex flex-col gap-3.5" style="padding-left: 30px">
{#each group.children as child (child.id)}
<Checkbox
checked={children[child.id]}
label={child.label}
onChange={(event) => onChildChange(child.id, event)}
/>
{/each}
</div>
</div>
ts
/** @jsxImportSource solid-js */
import { createMemo, createSignal } from "solid-js";
import { Checkbox } from "@grassroot/ui-solid";
const checkboxFixture = {
id: "checkbox",
options: [
{ id: "new-build", label: "New build", checked: true },
{ id: "rescue-job", label: "Rescue job", checked: false },
{ id: "unavailable-option", label: "Unavailable option", checked: false, disabled: true },
],
/**
* The parent/child group. The parent is never stored — it is DERIVED from
* its children on every render: all checked, none checked, or the third
* state in between. One child starts checked so the docs land on that third
* state rather than needing a click to reach it.
*/
group: {
parentLabel: "All regions",
children: [
{ id: "north", label: "North", checked: true },
{ id: "midlands", label: "Midlands", checked: false },
{ id: "south", label: "South", checked: false },
],
},
} satisfies {
id: string;
options: Array<{ id: string; label: string; checked: boolean; disabled?: boolean }>;
group: {
parentLabel: string;
children: Array<{ id: string; label: string; checked: boolean }>;
};
};
/**
* The parent's state, derived from its children. Shared by all five demos so
* the rule itself is written once and cannot drift between frameworks.
*
* Clicking a parent that is in the third state CHECKS every child rather than
* clearing them — the convention every checklist UI follows, and the one that
* cannot lose a selection the reader already made by accident.
*/
function checkboxGroupParentState(
children: Readonly<Record<string, boolean>>,
): boolean | "indeterminate" {
const values = Object.values(children);
if (values.every(Boolean)) return true;
if (values.some(Boolean)) return "indeterminate";
return false;
}
const group = checkboxFixture.group;
export default function CheckboxDemo() {
const [checked, setChecked] = createSignal(
Object.fromEntries(checkboxFixture.options.map((option) => [option.id, option.checked])),
);
const [children, setChildren] = createSignal(
Object.fromEntries(group.children.map((child) => [child.id, child.checked])),
);
// Never stored — the parent has no state of its own to fall out of sync.
const parent = createMemo(() => checkboxGroupParentState(children()));
return (
<div class="flex flex-col gap-3.5">
{checkboxFixture.options.map((option) => (
<Checkbox
label={option.label}
checked={checked()[option.id]}
disabled={option.disabled}
onChange={(event) =>
setChecked((current) => ({
...current,
[option.id]: (event.target as HTMLInputElement).checked,
}))
}
/>
))}
<Checkbox
label={group.parentLabel}
checked={parent()}
onChange={(event) =>
setChildren(
Object.fromEntries(
group.children.map((child) => [
child.id,
(event.target as HTMLInputElement).checked,
]),
),
)
}
/>
{/* Inline, not `pl-[30px]`: the docs app ships ui-core's precompiled
utilities and runs no Tailwind build, so an arbitrary class authored
here emits no rule. */}
<div class="flex flex-col gap-3.5" style={{ "padding-left": "30px" }}>
{group.children.map((child) => (
<Checkbox
label={child.label}
checked={children()[child.id]}
onChange={(event) =>
setChildren((current) => ({
...current,
[child.id]: (event.target as HTMLInputElement).checked,
}))
}
/>
))}
</div>
</div>
);
}

Installation

API Reference

Import

import { Checkbox } from "@grassroot/ui-react";
import * as React from "react";

Props

Prop
Type
Default
Requirement
Description
checked
boolean | "indeterminate" | undefined
optional
[C1] A third state, alongside `boolean`.
defaultChecked
boolean | "indeterminate" | undefined
optional
[C1] A third state, alongside `boolean`.
disabled
boolean | undefined
optional
readOnly
boolean | undefined
optional
name
string | undefined
optional
value
string | undefined
optional
required
boolean | undefined
optional
id
string | undefined
optional
Seeds the part scope, so the hidden input/label/description ids are all deterministic from it [baseline #10].
tone
Tone | undefined
"accent"
optional
Brand colour of the checked fill.
onCheckedChange
((detail: CheckboxChangeDetail) => void) | undefined
optional
onChange
React.ChangeEventHandler<HTMLInputElement, Element> | undefined
optional
"aria-label"
string | undefined
optional
Accessible name when neither `label` nor `description` is rendered.
className
string | undefined
optional
Prop
checked
Type
boolean | "indeterminate" | undefined
Default
Requirement
optional
Description
[C1] A third state, alongside `boolean`.
Prop
defaultChecked
Type
boolean | "indeterminate" | undefined
Default
Requirement
optional
Description
[C1] A third state, alongside `boolean`.
Prop
disabled
Type
boolean | undefined
Default
Requirement
optional
Description
Prop
readOnly
Type
boolean | undefined
Default
Requirement
optional
Description
Prop
name
Type
string | undefined
Default
Requirement
optional
Description
Prop
value
Type
string | undefined
Default
Requirement
optional
Description
Prop
required
Type
boolean | undefined
Default
Requirement
optional
Description
Prop
id
Type
string | undefined
Default
Requirement
optional
Description
Seeds the part scope, so the hidden input/label/description ids are all deterministic from it [baseline #10].
Prop
tone
Type
Tone | undefined
Default
"accent"
Requirement
optional
Description
Brand colour of the checked fill.
Prop
onCheckedChange
Type
((detail: CheckboxChangeDetail) => void) | undefined
Default
Requirement
optional
Description
Prop
onChange
Type
React.ChangeEventHandler<HTMLInputElement, Element> | undefined
Default
Requirement
optional
Description
Prop
"aria-label"
Type
string | undefined
Default
Requirement
optional
Description
Accessible name when neither `label` nor `description` is rendered.
Prop
className
Type
string | undefined
Default
Requirement
optional
Description

Events

EventPayload
onCheckedChange((detail: CheckboxChangeDetail) => void) | undefined

Content regions

  • children
  • description
  • label

Native attributes

Standard DOM and ARIA attributes not listed above spread onto the root element.

Import

import { computed, ref } from "vue";
import { Checkbox } from "@grassroot/ui-vue";

Props

Prop
Type
Default
Requirement
checked
CheckboxCheckedInput
undefined
optional
defaultChecked
CheckboxCheckedInput
undefined
optional
disabled
boolean | undefined
undefined
optional
readOnly
boolean | undefined
undefined
optional
name
string | undefined
undefined
optional
value
string | undefined
undefined
optional
required
boolean | undefined
undefined
optional
id
string | undefined
undefined
optional
ariaLabel
string | undefined
undefined
optional
tone
Tone
"accent"
optional
Prop
checked
Type
CheckboxCheckedInput
Default
undefined
Requirement
optional
Prop
defaultChecked
Type
CheckboxCheckedInput
Default
undefined
Requirement
optional
Prop
disabled
Type
boolean | undefined
Default
undefined
Requirement
optional
Prop
readOnly
Type
boolean | undefined
Default
undefined
Requirement
optional
Prop
name
Type
string | undefined
Default
undefined
Requirement
optional
Prop
value
Type
string | undefined
Default
undefined
Requirement
optional
Prop
required
Type
boolean | undefined
Default
undefined
Requirement
optional
Prop
id
Type
string | undefined
Default
undefined
Requirement
optional
Prop
ariaLabel
Type
string | undefined
Default
undefined
Requirement
optional
Prop
tone
Type
Tone
Default
"accent"
Requirement
optional

Events

EventPayload
checked-change(_detail: CheckboxChangeDetail) => true
change(_event: Event) => true

Content regions

  • default
  • description
  • label

Import

import { Component, computed, signal } from "@angular/core";
import { GrassrootCheckbox } from "@grassroot/ui-angular";

Props

Prop
Type
Default
Requirement
label
string
optional
description
string
optional
checked
boolean | "indeterminate"
optional
defaultChecked
boolean | "indeterminate"
optional
disabled
boolean
optional
readOnly
boolean
optional
name
string
optional
value
string
optional
required
boolean
optional
"aria-label"
string
undefined
optional
id
string
optional
tone
Tone
"accent"
optional
Prop
label
Type
string
Default
Requirement
optional
Prop
description
Type
string
Default
Requirement
optional
Prop
checked
Type
boolean | "indeterminate"
Default
Requirement
optional
Prop
defaultChecked
Type
boolean | "indeterminate"
Default
Requirement
optional
Prop
disabled
Type
boolean
Default
Requirement
optional
Prop
readOnly
Type
boolean
Default
Requirement
optional
Prop
name
Type
string
Default
Requirement
optional
Prop
value
Type
string
Default
Requirement
optional
Prop
required
Type
boolean
Default
Requirement
optional
Prop
"aria-label"
Type
string
Default
undefined
Requirement
optional
Prop
id
Type
string
Default
Requirement
optional
Prop
tone
Type
Tone
Default
"accent"
Requirement
optional

Events

EventPayload
checkedChangeCheckboxChangeDetail
changeEvent

Content regions

  • default
  • description
  • label

Import

import { Checkbox } from "@grassroot/ui-svelte";

Props

Prop
Type
Default
Requirement
checked
boolean | "indeterminate"
optional
defaultChecked
boolean | "indeterminate"
optional
disabled
boolean
optional
readOnly
boolean
optional
name
string
optional
value
string
optional
required
boolean
optional
id
string
optional
ariaLabel
string
optional
tone
Tone
"accent"
optional
className
string
optional
onCheckedChange
(detail: CheckboxChangeDetail) => void
optional
onChange
(event: Event) => void
optional
Prop
checked
Type
boolean | "indeterminate"
Default
Requirement
optional
Prop
defaultChecked
Type
boolean | "indeterminate"
Default
Requirement
optional
Prop
disabled
Type
boolean
Default
Requirement
optional
Prop
readOnly
Type
boolean
Default
Requirement
optional
Prop
name
Type
string
Default
Requirement
optional
Prop
value
Type
string
Default
Requirement
optional
Prop
required
Type
boolean
Default
Requirement
optional
Prop
id
Type
string
Default
Requirement
optional
Prop
ariaLabel
Type
string
Default
Requirement
optional
Prop
tone
Type
Tone
Default
"accent"
Requirement
optional
Prop
className
Type
string
Default
Requirement
optional
Prop
onCheckedChange
Type
(detail: CheckboxChangeDetail) => void
Default
Requirement
optional
Prop
onChange
Type
(event: Event) => void
Default
Requirement
optional

Events

EventPayload
onCheckedChange(detail: CheckboxChangeDetail) => void
onChange(event: Event) => void

Content regions

  • children
  • description
  • label

Import

import { createMemo, createSignal } from "solid-js";
import { Checkbox } from "@grassroot/ui-solid";

Props

Prop
Type
Default
Requirement
checked
boolean | "indeterminate"
optional
defaultChecked
boolean | "indeterminate"
optional
disabled
boolean
optional
readOnly
boolean
optional
name
string
optional
value
string
optional
required
boolean
optional
id
string
optional
ariaLabel
string
optional
aria-label
string
optional
tone
Tone
"accent"
optional
onCheckedChange
(detail: CheckboxChangeDetail) => void
optional
onChange
(event: Event) => void
optional
Prop
checked
Type
boolean | "indeterminate"
Default
Requirement
optional
Prop
defaultChecked
Type
boolean | "indeterminate"
Default
Requirement
optional
Prop
disabled
Type
boolean
Default
Requirement
optional
Prop
readOnly
Type
boolean
Default
Requirement
optional
Prop
name
Type
string
Default
Requirement
optional
Prop
value
Type
string
Default
Requirement
optional
Prop
required
Type
boolean
Default
Requirement
optional
Prop
id
Type
string
Default
Requirement
optional
Prop
ariaLabel
Type
string
Default
Requirement
optional
Prop
aria-label
Type
string
Default
Requirement
optional
Prop
tone
Type
Tone
Default
"accent"
Requirement
optional
Prop
onCheckedChange
Type
(detail: CheckboxChangeDetail) => void
Default
Requirement
optional
Prop
onChange
Type
(event: Event) => void
Default
Requirement
optional

Events

EventPayload
onCheckedChange(detail: CheckboxChangeDetail) => void
onChange(event: Event) => void

Content regions

  • children
  • description
  • label