Skip to main content

Checkbox

Capture an independent checked, unchecked, or mixed choice.

Use Checkbox for an independent boolean or mapped choice that can be selected alongside other choices. Use Switch when the control represents an immediate on/off setting.

Basic usage#

import { Checkbox } from 'moraine'

export function Example() {
  return <Checkbox label="Accept terms" />
}

Playground#

I agree to the privacy policy and terms of service.

Props
Slots

Anatomy#

Checkbox [component; slot=root]
├── container [slot]
│   ├── input [internal]
│   └── control [slot]
│       └── indicator [slot]
│           └── icon [slot]
└── wrapper [slot]
    ├── label [slot]
    └── description [slot]

Usage#

State and form values#

Use checked with onCheckedChange for controlled state, or defaultChecked for an initial value. Set indeterminate from aggregate selection state; it changes the mixed presentation without adding a third submitted value.

trueValue and falseValue map checked state to domain values for callbacks and schema-bound forms. Native form submission uses the separate value prop only while checked; an unchecked checkbox contributes no entry.

import { Checkbox } from 'moraine'
import { createSignal } from 'solid-js'

export function StateAndValues() {
  const [checked, setChecked] = createSignal(true)

  return (
    <div class="flex flex-col gap-3">
      <Checkbox
        checked={checked()}
        onCheckedChange={setChecked}
        label="Subscribe to product updates"
        description="Receive weekly summaries of new releases and features."
      />
      <p class="text-xs text-muted-foreground">
        Current state:{' '}
        <span class="text-foreground font-medium">{checked() ? 'checked' : 'unchecked'}</span>
      </p>
    </div>
  )
}

Read-only and disabled#

disabled removes the control from normal interaction. readOnly preserves the value and focusability while preventing changes.

import { Checkbox } from 'moraine'

export function DisabledReadonly() {
  return (
    <div class="flex flex-col gap-4">
      <Checkbox
        disabled
        defaultChecked
        label="Disabled (checked)"
        description="Non-interactive and removed from form submission."
      />
      <Checkbox
        readOnly
        defaultChecked
        label="Read-only (checked)"
        description="Focusable and submitted, but prevents value changes."
      />
    </div>
  )
}

Examples#

Indeterminate and custom indicator#

import { Checkbox } from 'moraine'
import { createMemo, createSignal, For } from 'solid-js'

export function IndeterminateCustomIcons() {
  const [tasks, setTasks] = createSignal([
    { id: 'tests', label: 'Run unit and integration test suites', checked: true },
    { id: 'build', label: 'Build static assets and server bundle', checked: true },
    { id: 'migrate', label: 'Apply pending database migrations', checked: false },
  ])

  const checkedCount = createMemo(() => tasks().filter((t) => t.checked).length)
  const allChecked = createMemo(() => checkedCount() === tasks().length)
  const isIndeterminate = createMemo(() => checkedCount() > 0 && checkedCount() < tasks().length)

  const parentState = createMemo<'indeterminate' | boolean>(() => {
    if (isIndeterminate()) {
      return 'indeterminate'
    }
    return allChecked()
  })

  const toggleAll = () => {
    const nextState = !allChecked()
    setTasks((current) => current.map((task) => ({ ...task, checked: nextState })))
  }

  const toggleTask = (id: string) => {
    setTasks((current) =>
      current.map((task) => (task.id === id ? { ...task, checked: !task.checked } : task)),
    )
  }

  return (
    <div class="p-4 b-1 b-border rounded-xl max-w-xl space-y-4">
      <Checkbox
        label="Production deployment checklist"
        description={`${checkedCount()} of ${tasks().length} tasks completed`}
        checked={parentState()}
        onCheckedChange={toggleAll}
        checkedIcon="i-lucide:check-check"
        indeterminateIcon="i-lucide:minus"
      />

      <div class="pl-6 border-border border-l-2 space-y-2">
        <For each={tasks()}>
          {(task) => (
            <Checkbox
              size="sm"
              label={task.label}
              checked={task.checked}
              onCheckedChange={() => toggleTask(task.id)}
            />
          )}
        </For>
      </div>
    </div>
  )
}

Custom true and false values#

import { Badge, Checkbox } from 'moraine'
import { createSignal } from 'solid-js'

export function CustomTrueFalseValues() {
  const [telemetry, setTelemetry] = createSignal<'opted-in' | 'opted-out'>('opted-in')

  return (
    <div class="p-4 b-1 b-border rounded-xl max-w-xl space-y-4">
      <div class="flex gap-4 items-center justify-between">
        <Checkbox<'opted-in', 'opted-out'>
          label="Anonymous telemetry collection"
          description="Send anonymous crash diagnostics and performance reports."
          trueValue="opted-in"
          falseValue="opted-out"
          checked={telemetry()}
          onCheckedChange={setTelemetry}
        />
        <Badge variant={telemetry() === 'opted-in' ? 'subtle' : 'outline'}>{telemetry()}</Badge>
      </div>

      <p class="text-xs text-muted-foreground">
        Controlled domain state is stored as <code class="font-mono">{telemetry()}</code> instead of
        a raw boolean.
      </p>
    </div>
  )
}

Form integration#

import { Button, Checkbox, createForm } from 'moraine'
import { createSignal } from 'solid-js'
import * as v from 'valibot'

export function FormIntegration() {
  const [submitted, setSubmitted] = createSignal<boolean | null>(null)
  const form = createForm({
    schema: v.object({
      terms: v.pipe(v.boolean(), v.literal(true, 'You must accept the terms to proceed.')),
    }),
    initialInput: { terms: false },
    validate: 'input',
  })

  return (
    <form.Form onSubmit={(output) => setSubmitted(output.terms)}>
      <div class="max-w-xl space-y-4">
        <form.Field name="terms" description="Required for account activation." required>
          <Checkbox label="I agree to the Terms of Service and Privacy Policy" />
        </form.Field>
        <div class="flex gap-3 items-center">
          <Button type="submit" variant="secondary" size="sm">
            Validate
          </Button>
          <p class="text-xs text-muted-foreground">
            Terms accepted: {submitted() === null ? 'Pending' : submitted() ? 'Yes' : 'No'}
          </p>
        </div>
      </div>
    </form.Form>
  )
}

Attributes#

Attributes
data-checkedSlot: checkbox, checkbox-control, checkbox-indicatorDescription: Present when the item or toggle is checked.
data-disabledSlot: checkbox, checkbox-control, checkbox-indicatorDescription: Present when the component, slot, or item is disabled.
data-indeterminateSlot: checkbox, checkbox-control, checkbox-indicatorDescription: Present when the checkbox or progress state is indeterminate.
data-invalidSlot: checkbox, checkbox-controlDescription: Present when the field or form has a validation error.
data-readonlySlot: checkbox, checkbox-controlDescription: Present when the field is in read-only mode.
data-requiredSlot: checkbox, checkbox-control, checkbox-labelDescription: Present when the field input is required.
data-uncheckedSlot: checkbox, checkbox-controlDescription: Present when the checkbox, radio item, or switch is unchecked.

Props#

Props for the Checkbox component. Renders a <div> element by default.

Prop