Skip to content
omaris

Selection

Checkbox

MD3 checkbox.

import { Checkbox } from 'omaris'
Learn

Examples

Basic

A choice submitted with the form. The label is a prop wired to the control, so clicking the text toggles it too.

<script lang="ts">	import { Checkbox } from 'omaris';​	let agreed = $state(false);</script>​<Checkbox label="Email me about outages" bind:checked={agreed} />

Description and states

indeterminate is the parent of a partly-checked set, not a third value.

<script lang="ts">	import { Checkbox } from 'omaris';​	let email = $state<boolean>(true);	let sms = $state<boolean>(false);​	const all = $derived(email && sms);	const some = $derived((email || sms) && !all);​	function toggleAll() {		const next = !all;		email = next;		sms = next;	}</script>​<div class="flex flex-col gap-3">	<Checkbox		label="Notifications"		description="Two channels available"		checked={all}		indeterminate={some}		onchange={toggleAll}	/>	<div class="flex flex-col gap-3 ps-7">		<Checkbox label="Email" bind:checked={email} />		<Checkbox label="SMS" bind:checked={sms} />	</div>	<Checkbox label="Disabled" disabled />	<Checkbox label="Invalid" invalid description="You have to accept the terms." /></div>

Tones and sizes

Any colour role — success for a consent, destructive for a dangerous opt-in — and lg where a thumb needs the room.

<script lang="ts">	import { Checkbox } from 'omaris';</script>​<Checkbox label="primary" checked /><Checkbox label="success" tone="success" checked /><Checkbox label="destructive" tone="destructive" checked /><Checkbox label="Large" size="lg" checked />

Drag to paint

Press one box and drag down the column: every box the pointer crosses lands on the state the first one was heading to. On a touch screen, hold for a moment first so a scroll stays a scroll.

2 of 8 connected

<script lang="ts">	import { Checkbox, Text } from 'omaris';​	const services = ['Vercel', 'Linear', 'GitHub', 'Figma', 'Stripe', 'Notion', 'Dropbox', 'Asana'];​	let connected = $state<string[]>(['GitHub', 'Figma']);​	function toggle(service: string) {		connected = connected.includes(service)			? connected.filter((entry) => entry !== service)			: [...connected, service];	}</script>​<div class="w-full max-w-xs">	<ul class="divide-y divide-border overflow-hidden rounded-lg border border-border">		{#each services as service (service)}			{@const on = connected.includes(service)}			<li				class="flex items-center gap-2 px-2 transition-colors duration-150 ease-standard motion-reduce:transition-none"				class:bg-primary-container={on}			>				<Checkbox label={service} checked={on} onchange={() => toggle(service)} />			</li>		{/each}	</ul>	<Text variant="body-sm" class="mt-2 text-muted-foreground">		{connected.length} of {services.length} connected	</Text></div>

When to use it

Use it for

  • Any number of items from a set: permissions, columns to show, rows to act on.
  • A yes/no submitted with the form rather than applied at once: "Accept the terms", "Send me the digest". Bind checked and give it a name.
  • The parent row over a set. indeterminate while some but not all children are checked. The parent computes it; it is not a third value.
  • An option that needs explaining. description is the second line that says what the first commits you to.
  • A run of rows ticked in one go. Dragging across a column of checkboxes paints them all to the state the first one was heading to, the way a phone's photo grid selects. It is on by default and needs no wiring.

Not for

  • A setting that takes effect the moment it flips → Switch.
  • Exactly one of several → Radio.
  • A filter above a list that shows its result as you tick → filter Chips.
  • A single boolean in a toolbar → a toggle Icon Button.
  • More than about seven options → a searchable Combobox.

Do

  • Use label (or children for rich text) rather than a separate <label>. The text is wired to the input, so clicking it toggles the box.
  • Keep size="md", the 40dp target, anywhere a thumb will land. sm is for a dense table cell.
  • Mark a required-but-empty box invalid and say why in text beside it. The red border is not an error message.
  • Compute the parent's indeterminate from the children and let a click on the dash check everything.
  • Keep a list of checkboxes in one container — a ul, a form, a table — so a paint stroke knows where it ends. Across containers you don't control, give every box in the run the same paint="name".

Don't

  • Phrase the label as a negative. "Don't email me" makes a tick mean no.
  • Use a column of checkboxes as a settings list. That list is Switch rows.
  • Restyle the box through classes until the empty state has no border. An unchecked box must still be visible.
  • Turn paint off because a row does something on change. If ticking a box fires a request, the fix is batching the result, not making people press twelve times.

Quick reference

tone
  • primary (default)
  • secondary
  • tertiary
  • destructive
  • success
  • warning
  • info
size
  • md (default)
  • lg

API

Checkbox

MD3 checkbox.

A real <input type="checkbox"> does the work — keyboard, form submission, indeterminate — and is drawn over with a box that fills from the center when checked. The 40dp state layer around the 18dp box is the MD3 touch target; it only shows on hover, focus and press.

import { Checkbox } from 'omaris'

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
No description in the source yet.
control
Wrapper that holds the state layer and the box.
box
The visible square: the outline, and nothing else.
fill
The colour, on a layer of its own so it can grow from the middle rather than snap on. Scaling a solid square is one compositor property — it stays at 60fps in a list of a hundred rows, which animating background-color would not.
input
The <input> itself: invisible, but still the thing you click.
mark
The tick / dash. Both paths are always in the DOM and are drawn on by walking stroke-dashoffset down to zero, so the tick writes itself in the direction a hand would and the dash slides out from the centre — a mounted {#if} could only fade.
label
No description in the source yet.
description
No description in the source yet.

Props

tone

Defaults to 'primary'

CheckboxTone
primary
secondary
tertiary
destructive
success
warning
info
size

Defaults to 'md'

CheckboxSize
md
lg
checked bindable

Defaults to false

boolean

Bindable.

indeterminate

Defaults to false

boolean

The dash state — some but not all children are checked.

label
string

Text beside the box.

description
string

Second line under the label.

invalid
boolean

Inside a Field, defaults to the field's.

haptics

Defaults to true

boolean

A haptic tap when it's ticked or cleared. Follows the shared haptics store; false opts this checkbox out.

sound

Defaults to true

boolean

A sound cue when it's ticked. Follows the shared sound store; false opts this checkbox out.

paint

Defaults to true

boolean | string

Drag across a run of checkboxes to set them all, the way a phone's photo grid selects. The box you press decides the direction — start on an empty one and the stroke ticks, start on a ticked one and it clears. On by default.

A stroke stays inside its container (the nearest list, form, table or fieldset); pass a name to paint across one you don't control, and the same name has to be on every box in the run. false opts a checkbox out of being painted and of starting a stroke.

class
string
classes
CheckboxClasses

Per-part Tailwind overrides. class still covers the root.

children
Snippet

Rich label, in place of label.

ref bindable

Defaults to null

HTMLInputElement | null