Checkbox
Form control primitive for boolean and mixed selection state. When omitting visible label text in a list or table, supply label to give the native checkbox an accessible name.
Default
With no state properties supplied, Checkbox starts unchecked and enabled.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-checkbox/ui-checkbox.html"
>
<ui-checkbox>Notify Sam</ui-checkbox><script setup>
import { UiCheckbox } from "@threadlabs/looma";
</script>
<template>
<UiCheckbox>Notify Sam</UiCheckbox>
</template>import { UiCheckbox } from "@threadlabs/looma";
export default function Example() {
return <UiCheckbox>Notify Sam</UiCheckbox>;
}/** @jsxImportSource preact */
import { UiCheckbox } from "@threadlabs/looma";
export default function Example() {
return <UiCheckbox>Notify Sam</UiCheckbox>;
}<script>
import { UiCheckbox } from "@threadlabs/looma";
</script>
<UiCheckbox>Notify Sam</UiCheckbox>import { Component } from "@angular/core";
import { UiCheckbox } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCheckbox],
template: `
<label
ui-checkbox
>Notify Sam</label>
`,
})
export default class Example {}Select all and mixed state
Change an item to see the parent return to a mixed state. Clicking the parent selects or clears the whole group.
Code
<script
type="module"
>
import "@threadlabs/looma";
import { componentRegistry } from "@nextwebwg/html-next/runtime";
const all = document.querySelector("#docs-checkbox-all");
const items = [...document.querySelectorAll(".docs-checkbox-item")];
const isChecked = (item) => item.querySelector('input[type="checkbox"]').checked;
all.addEventListener("change", (event) => {
if (typeof event.detail?.checked !== "boolean") return;
for (const item of items) componentRegistry.update(item, { checked: event.detail.checked });
componentRegistry.update(all, { indeterminate: false });
});
for (const item of items) item.addEventListener("change", (event) => {
if (typeof event.detail?.checked !== "boolean") return;
const count = items.filter(isChecked).length;
componentRegistry.update(all, { checked: count === items.length, indeterminate: count > 0 && count < items.length });
});
</script>
<ui-stack
gap="s"
>
<ui-checkbox
id="docs-checkbox-all"
indeterminate
>Select all</ui-checkbox>
<ui-stack
gap="xs"
style="padding-inline-start: 1.5rem"
>
<ui-checkbox
class="docs-checkbox-item"
checked
>Design</ui-checkbox>
<ui-checkbox
class="docs-checkbox-item"
>Build</ui-checkbox>
<ui-checkbox
class="docs-checkbox-item"
>Review</ui-checkbox>
</ui-stack>
</ui-stack>
<script setup>
import { UiCheckbox, UiStack } from "@threadlabs/looma";
</script>
<template>
<UiStack
gap="s"
>
<UiCheckbox
id="docs-checkbox-all"
data-example-id="docs-checkbox-all"
:indeterminate="true"
>Select all</UiCheckbox>
<UiStack
gap="xs"
style="padding-inline-start: 1.5rem"
>
<UiCheckbox
class="docs-checkbox-item"
:checked="true"
>Design</UiCheckbox>
<UiCheckbox
class="docs-checkbox-item"
>Build</UiCheckbox>
<UiCheckbox
class="docs-checkbox-item"
>Review</UiCheckbox>
</UiStack>
</UiStack>
</template>import { UiCheckbox, UiStack } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="s"
>
<UiCheckbox
id="docs-checkbox-all"
data-example-id="docs-checkbox-all"
indeterminate
>Select all</UiCheckbox>
<UiStack
gap="xs"
style={{"paddingInlineStart":"1.5rem"}}
>
<UiCheckbox
className="docs-checkbox-item"
checked
>Design</UiCheckbox>
<UiCheckbox
className="docs-checkbox-item"
>Build</UiCheckbox>
<UiCheckbox
className="docs-checkbox-item"
>Review</UiCheckbox>
</UiStack>
</UiStack>;
}/** @jsxImportSource preact */
import { UiCheckbox, UiStack } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="s"
>
<UiCheckbox
id="docs-checkbox-all"
data-example-id="docs-checkbox-all"
indeterminate
>Select all</UiCheckbox>
<UiStack
gap="xs"
style={{"paddingInlineStart":"1.5rem"}}
>
<UiCheckbox
className="docs-checkbox-item"
checked
>Design</UiCheckbox>
<UiCheckbox
className="docs-checkbox-item"
>Build</UiCheckbox>
<UiCheckbox
className="docs-checkbox-item"
>Review</UiCheckbox>
</UiStack>
</UiStack>;
}<script>
import { UiCheckbox, UiStack } from "@threadlabs/looma";
</script>
<UiStack
gap="s"
>
<UiCheckbox
id="docs-checkbox-all"
data-example-id="docs-checkbox-all"
indeterminate
>Select all</UiCheckbox>
<UiStack
gap="xs"
style="padding-inline-start: 1.5rem"
>
<UiCheckbox
class="docs-checkbox-item"
checked
>Design</UiCheckbox>
<UiCheckbox
class="docs-checkbox-item"
>Build</UiCheckbox>
<UiCheckbox
class="docs-checkbox-item"
>Review</UiCheckbox>
</UiStack>
</UiStack>import { Component } from "@angular/core";
import { UiCheckbox, UiStack } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiStack, UiCheckbox],
template: `
<div
ui-stack
[gap]="'s'"
>
<label
ui-checkbox
id="docs-checkbox-all"
data-example-id="docs-checkbox-all"
[indeterminate]="true"
>Select all</label>
<div
ui-stack
[gap]="'xs'"
style="padding-inline-start: 1.5rem"
>
<label
ui-checkbox
class="docs-checkbox-item"
[checked]="true"
>Design</label>
<label
ui-checkbox
class="docs-checkbox-item"
>Build</label>
<label
ui-checkbox
class="docs-checkbox-item"
>Review</label>
</div>
</div>
`,
})
export default class Example {}Multi-line label
The control stays aligned to the first line while long label text wraps at a constrained width.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-checkbox/ui-checkbox.html"
>
<div
style="max-inline-size: 18rem"
>
<ui-checkbox>Send me product updates, release notes, and occasional research invitations.</ui-checkbox>
</div><script setup>
import { UiCheckbox } from "@threadlabs/looma";
</script>
<template>
<div
style="max-inline-size: 18rem"
>
<UiCheckbox>Send me product updates, release notes, and occasional research invitations.</UiCheckbox>
</div>
</template>import { UiCheckbox } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxInlineSize":"18rem"}}
>
<UiCheckbox>Send me product updates, release notes, and occasional research invitations.</UiCheckbox>
</div>;
}/** @jsxImportSource preact */
import { UiCheckbox } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxInlineSize":"18rem"}}
>
<UiCheckbox>Send me product updates, release notes, and occasional research invitations.</UiCheckbox>
</div>;
}<script>
import { UiCheckbox } from "@threadlabs/looma";
</script>
<div
style="max-inline-size: 18rem"
>
<UiCheckbox>Send me product updates, release notes, and occasional research invitations.</UiCheckbox>
</div>import { Component } from "@angular/core";
import { UiCheckbox } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCheckbox],
template: `
<div
style="max-inline-size: 18rem"
>
<label
ui-checkbox
>Send me product updates, release notes, and occasional research invitations.</label>
</div>
`,
})
export default class Example {}name, value, and required
Name and value are what the form submits when the box is checked (value defaults to "on"), and an unchecked box sends nothing; required blocks submission until it is checked.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-checkbox/ui-checkbox.html"
>
<form>
<ui-checkbox
name="terms"
value="accepted"
required
>I accept the terms</ui-checkbox>
</form><script setup>
import { UiCheckbox } from "@threadlabs/looma";
</script>
<template>
<form>
<UiCheckbox
name="terms"
value="accepted"
:required="true"
>I accept the terms</UiCheckbox>
</form>
</template>import { UiCheckbox } from "@threadlabs/looma";
export default function Example() {
return <form>
<UiCheckbox
name="terms"
value="accepted"
required
>I accept the terms</UiCheckbox>
</form>;
}/** @jsxImportSource preact */
import { UiCheckbox } from "@threadlabs/looma";
export default function Example() {
return <form>
<UiCheckbox
name="terms"
value="accepted"
required
>I accept the terms</UiCheckbox>
</form>;
}<script>
import { UiCheckbox } from "@threadlabs/looma";
</script>
<form>
<UiCheckbox
name="terms"
value="accepted"
required
>I accept the terms</UiCheckbox>
</form>import { Component } from "@angular/core";
import { UiCheckbox } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCheckbox],
template: `
<form>
<label
ui-checkbox
[name]="'terms'"
[value]="'accepted'"
[required]="true"
>I accept the terms</label>
</form>
`,
})
export default class Example {}With a description
The description slot says what the choice means, on a line under its label. It is the input's accessible description, not part of its name.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-checkbox/ui-checkbox.html"
>
<ui-checkbox
name="digest"
checked
>
Weekly digest
<span
slot="description"
>A summary of what changed in your workspaces, every Monday.</span>
</ui-checkbox><script setup>
import { UiCheckbox } from "@threadlabs/looma";
</script>
<template>
<UiCheckbox
name="digest"
:checked="true"
>Weekly digest<template #description>
<span>A summary of what changed in your workspaces, every Monday.</span>
</template></UiCheckbox>
</template>import { UiCheckbox } from "@threadlabs/looma";
export default function Example() {
return <UiCheckbox
name="digest"
checked
slots={{
description: <span>A summary of what changed in your workspaces, every Monday.</span>,
}}
>Weekly digest</UiCheckbox>;
}/** @jsxImportSource preact */
import { UiCheckbox } from "@threadlabs/looma";
export default function Example() {
return <UiCheckbox
name="digest"
checked
slots={{
description: <span>A summary of what changed in your workspaces, every Monday.</span>,
}}
>Weekly digest</UiCheckbox>;
}<script>
import { UiCheckbox } from "@threadlabs/looma";
</script>
{#snippet description()}
<span>A summary of what changed in your workspaces, every Monday.</span>
{/snippet}
<UiCheckbox
name="digest"
checked
slots={{ description }}
>Weekly digest</UiCheckbox>import { Component } from "@angular/core";
import { UiCheckbox } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCheckbox],
template: `
<label
ui-checkbox
[name]="'digest'"
[checked]="true"
>Weekly digest<ng-container ngProjectAs="[slot='description']">
<span>A summary of what changed in your workspaces, every Monday.</span>
</ng-container></label>
`,
})
export default class Example {}size
size="sm" keeps the checkbox's box and centres its label in the small control height, in the small controls' type size, so it lines up with a small button.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-checkbox/ui-checkbox.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<ui-cluster
gap="s"
align="center"
>
<ui-checkbox
size="sm"
>Checkbox</ui-checkbox>
<ui-button
size="sm"
>Small button</ui-button>
</ui-cluster><script setup>
import { UiButton, UiCheckbox, UiCluster } from "@threadlabs/looma";
</script>
<template>
<UiCluster
gap="s"
align="center"
>
<UiCheckbox
size="sm"
>Checkbox</UiCheckbox>
<UiButton
size="sm"
>Small button</UiButton>
</UiCluster>
</template>import { UiButton, UiCheckbox, UiCluster } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
gap="s"
align="center"
>
<UiCheckbox
size="sm"
>Checkbox</UiCheckbox>
<UiButton
size="sm"
>Small button</UiButton>
</UiCluster>;
}/** @jsxImportSource preact */
import { UiButton, UiCheckbox, UiCluster } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
gap="s"
align="center"
>
<UiCheckbox
size="sm"
>Checkbox</UiCheckbox>
<UiButton
size="sm"
>Small button</UiButton>
</UiCluster>;
}<script>
import { UiButton, UiCheckbox, UiCluster } from "@threadlabs/looma";
</script>
<UiCluster
gap="s"
align="center"
>
<UiCheckbox
size="sm"
>Checkbox</UiCheckbox>
<UiButton
size="sm"
>Small button</UiButton>
</UiCluster>import { Component } from "@angular/core";
import { UiButton, UiCheckbox, UiCluster } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCluster, UiCheckbox, UiButton],
template: `
<div
ui-cluster
[gap]="'s'"
[align]="'center'"
>
<label
ui-checkbox
[size]="'sm'"
>Checkbox</label>
<ng-template
ui-button
[size]="'sm'"
>Small button</ng-template>
</div>
`,
})
export default class Example {}Invalid choice
Invalid marks a choice whose validation is managed by the form and uses the field danger colour.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-checkbox/ui-checkbox.html"
>
<ui-checkbox
invalid
name="consent"
>I agree to continue</ui-checkbox><script setup>
import { UiCheckbox } from "@threadlabs/looma";
</script>
<template>
<UiCheckbox
:invalid="true"
name="consent"
>I agree to continue</UiCheckbox>
</template>import { UiCheckbox } from "@threadlabs/looma";
export default function Example() {
return <UiCheckbox
invalid
name="consent"
>I agree to continue</UiCheckbox>;
}/** @jsxImportSource preact */
import { UiCheckbox } from "@threadlabs/looma";
export default function Example() {
return <UiCheckbox
invalid
name="consent"
>I agree to continue</UiCheckbox>;
}<script>
import { UiCheckbox } from "@threadlabs/looma";
</script>
<UiCheckbox
invalid
name="consent"
>I agree to continue</UiCheckbox>import { Component } from "@angular/core";
import { UiCheckbox } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCheckbox],
template: `
<label
ui-checkbox
[invalid]="true"
[name]="'consent'"
>I agree to continue</label>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: label
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
checked | Whether the checkbox is checked initially or when set externally. A form reset returns it to this. | checked | boolean | false | — |
disabled | Disables the native checkbox, so it cannot be toggled or focused. | disabled | boolean | false | — |
indeterminate | Shows the mixed state, for a checkbox that summarises a group whose items are partly checked. It sets the native indeterminate property, which is independent of checked, so update it as the group changes. | indeterminate | boolean | false | — |
invalid | Exposes an externally validated error and styles the checkbox with the field danger colour. | invalid | boolean | false | — |
label | An accessible name for a checkbox without a visible label slot. It names the native input. | label | string | | — |
name | The native name the checkbox submits under: a form that contains it sends name=value while it is checked, and nothing while it is unchecked, as a native checkbox does. Left empty, it takes no part in form submission. | name | string | | — |
required | Marks the native checkbox required, so native form validation treats it as invalid until it is checked. | required | boolean | false | — |
size | Aligns the checkbox with fields and buttons of the same size. It keeps its box size; the label takes that size's field type size and its first line centres in that control height (sm 2rem, lg 3rem by default), so in a row of inputs, selects, and buttons of one size it shares their height and their text baseline. Touch input keeps the same visible alignment. md is the standard layout. | size | keyword | md | sm, md, lg |
value | The value a form submits under name while the checkbox is checked, also reported in the change event's detail so one handler can tell checkboxes apart. on by default, as on a native checkbox. | value | string | on | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
checked | boolean | false | — |
disabled | boolean | false | — |
indeterminate | boolean | false | — |
invalid | boolean | false | — |
label | string | | — |
name | string | | — |
required | boolean | false | — |
size | "sm" | "md" | "lg" | md | sm, md, lg |
value | string | on | — |
Events
| Name | Detail | Description |
|---|---|---|
change | object
| Emitted when a toggleable control changes checked state. |
Slots
| Name | Purpose |
|---|---|
default | Default child content. |
description | Named content for the description region. |
Design tokens
Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.
Component tokens
| Token | Declared values | Fallback |
|---|---|---|
--ui-checkbox-gap | — | var(--ui-space-2) |
--ui-checkbox-invalid-border | — | var(--ui-field-danger, var(--ui-danger-solid)) |
--ui-checkbox-size | — | var(--ui-choice-mark-size) | 1.125rem |
Shared tokens
| Token | Fallback |
|---|---|
--ui-accent-highlight | none |
--ui-accent-solid | — |
--ui-border-strong | — |
--ui-border-width | — |
--ui-choice-hover-highlight | none |
--ui-choice-hover-surface | — |
--ui-choice-mark-size | — |
--ui-control-border | var(--ui-border-strong) |
--ui-control-border-hover | var(--ui-border-strong) |
--ui-control-inset | — |
--ui-control-size-lg | — |
--ui-control-size-sm | — |
--ui-control-surface | var(--ui-surface-default) |
--ui-danger-solid | — |
--ui-disabled-border | — |
--ui-disabled-surface | — |
--ui-disabled-text | — |
--ui-field-danger | var(--ui-danger-solid) |
--ui-focus-ring | var(--ui-accent-solid) |
--ui-focus-width | — |
--ui-font-size-md | — |
--ui-font-size-sm | — |
--ui-line-height | — |
--ui-line-height-md | — |
--ui-motion-base | — |
--ui-motion-ease | — |
--ui-motion-ease-out | cubic-bezier(0.16, 1, 0.3, 1) |
--ui-motion-fast | — |
--ui-neutral-wash-hover | — |
--ui-radius-round | — |
--ui-radius-sm | — |
--ui-space-1 | — |
--ui-space-2 | — |
--ui-surface-default | — |
--ui-surface-highlight | none | linear-gradient(transparent, transparent) |
--ui-text-on-accent | — |
--ui-text-primary | — |
--ui-text-secondary | — |