---
title: CheckboxGroup
description: Collect multiple choices as one array-valued field.
package: moraine
version: 0.6.0
repository: https://github.com/subframe7536/moraine
---

# CheckboxGroup

> Collect multiple choices as one array-valued field.

Use CheckboxGroup for several choices submitted as one array. Use individual [Checkbox](https://moraine.subf.dev/components/checkbox.md) controls when the fields are independent.

## Basic usage

```tsx
import { CheckboxGroup } from 'moraine'

export function Example() {
  return (
    <CheckboxGroup
      aria-label="Preferences"
      items={[
        { value: 'email', label: 'Email' },
        { value: 'sms', label: 'SMS' },
      ]}
    />
  )
}
```

## Anatomy

```text
CheckboxGroup [component; slot=root; <div>]
└── fieldset [slot]
    ├── legend [slot]
    └── item [slot]
        ├── container [slot]
        │   ├── input [internal]
        │   └── control [slot]
        │       └── indicator [slot]
        │           └── icon [slot]
        └── wrapper [slot]
            ├── label [slot]
            └── description [slot]
```

The nested `<fieldset>` owns group semantics and the optional `<legend>` association.

## Usage

### Selection model

The group value is an array of item values. Pair `value` with `onValueChange` for controlled selection, or use a default value when the group owns its initial state. A disabled group disables all items; an item can also be disabled independently.

```tsx
import { CheckboxGroup } from 'moraine'
import { createSignal } from 'solid-js'

const NOTIFICATION_OPTIONS = [
  {
    label: 'Email alerts',
    value: 'email',
    description: 'Immediate notifications for critical events.',
  },
  {
    label: 'Weekly digest',
    value: 'digest',
    description: 'Summary of team activity and metric trends.',
  },
  { label: 'SMS updates', value: 'sms', description: 'Urgent security and billing alerts only.' },
]

export function SelectionModel() {
  const [selected, setSelected] = createSignal<string[]>(['email', 'digest'])

  return (
    <div class="flex flex-col gap-3">
      <CheckboxGroup
        legend="Notification preferences"
        items={NOTIFICATION_OPTIONS}
        value={selected()}
        onValueChange={setSelected}
      />
      <p class="text-xs text-muted-foreground">
        Selected values: <span class="text-foreground font-mono">{JSON.stringify(selected())}</span>
      </p>
    </div>
  )
}
```

### Forms

Give the group a `name` for native form submission, or place it inside `form.Field` from
[createForm](https://moraine.subf.dev/components/form.md) for schema binding. Standalone [Field](https://moraine.subf.dev/components/field.md) supplies
labels and messages without validation. Each checkbox keeps its own tab stop; the group does not
use radio-style arrow navigation.

```tsx
import { CheckboxGroup } from 'moraine'

const ROLE_OPTIONS = [
  { label: 'Read repository', value: 'read' },
  { label: 'Write changes', value: 'write' },
  { label: 'Admin access', value: 'admin', disabled: true },
]

export function FormBehavior() {
  return (
    <div class="max-w-md">
      <CheckboxGroup name="permissions" items={ROLE_OPTIONS} defaultValue={['read']} />
    </div>
  )
}
```

## Examples

### Controlled value and disabled items

```tsx
import { CheckboxGroup } from 'moraine'
import { createSignal } from 'solid-js'

export function ControlledDisabledItems() {
  const ROLES = [
    { value: 'viewer', label: 'Viewer', description: 'Read-only access to repositories' },
    { value: 'developer', label: 'Developer', description: 'Can push commits and create PRs' },
    { value: 'maintainer', label: 'Maintainer', description: 'Can merge PRs and manage releases' },
    {
      value: 'owner',
      label: 'Owner (Immutable)',
      description: 'Primary organization administrator',
      disabled: true,
    },
  ]

  const [value, setValue] = createSignal<string[]>(['developer', 'owner'])

  return (
    <div class="p-4 b-1 b-border rounded-xl max-w-xl space-y-4">
      <CheckboxGroup
        legend="Team member role permissions"
        variant="card"
        items={ROLES}
        value={value()}
        onValueChange={setValue}
      />
      <p class="text-xs text-muted-foreground">
        Active roles: <span class="text-foreground font-medium">{value().join(', ')}</span>
      </p>
    </div>
  )
}
```

### Custom indicator

```tsx
import { CheckboxGroup } from 'moraine'
import { For } from 'solid-js'

const NOTIFICATIONS = [
  {
    value: 'mentions',
    label: 'Direct @mentions',
    description: 'When someone mentions you in a thread',
  },
  { value: 'assignee', label: 'Issue assigned', description: 'When an issue is assigned to you' },
  {
    value: 'review',
    label: 'Review requested',
    description: 'When your review is required on a PR',
  },
]

const INDICATORS = ['start', 'end', 'hidden'] as const

export function Indicator() {
  return (
    <div class="gap-4 grid md:grid-cols-2 xl:grid-cols-3">
      <For each={INDICATORS}>
        {(indicator) => (
          <div class="p-4 b-1 b-border rounded-xl space-y-2">
            <p class="text-xs text-muted-foreground tracking-wider font-semibold uppercase">
              Indicator: {indicator}
            </p>
            <CheckboxGroup
              legend="Activity alerts"
              items={NOTIFICATIONS}
              indicator={indicator}
              defaultValue={['mentions', 'review']}
            />
          </div>
        )}
      </For>
    </div>
  )
}
```

### Form integration

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

const ROLE_OPTIONS = [
  { label: 'Read repository', value: 'read' },
  { label: 'Write changes', value: 'write' },
  { label: 'Admin access', value: 'admin' },
]

export function FormIntegration() {
  const [submitted, setSubmitted] = createSignal<string[]>([])
  const form = createForm({
    schema: v.object({
      permissions: v.pipe(
        v.array(v.string()),
        v.minLength(1, 'Select at least one permission role.'),
      ),
    }),
    initialInput: { permissions: ['read'] },
    validate: 'input',
  })

  return (
    <form.Form onSubmit={(output) => setSubmitted(output.permissions)}>
      <div class="max-w-xl space-y-4">
        <form.Field
          name="permissions"
          label="Role permissions"
          description="Assigned permissions for team members."
          required
        >
          <CheckboxGroup items={ROLE_OPTIONS} />
        </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">
            Active roles: {submitted().length ? submitted().join(', ') : 'read'}
          </p>
        </div>
      </div>
    </form.Form>
  )
}
```

## Attributes

| Attributes | Slot | Description |
| --- | --- | --- |
| `data-disabled` | `checkbox-group`, `checkbox-group-item`, `checkbox-control`, `checkbox-indicator` | Present when the component, slot, or item is disabled. |
| `data-invalid` | `checkbox-group`, `checkbox-group-item`, `checkbox-control` | Present when the field or form has a validation error. |
| `data-readonly` | `checkbox-group`, `checkbox-group-item`, `checkbox-control` | Present when the field is in read-only mode. |
| `data-required` | `checkbox-group`, `checkbox-group-legend`, `checkbox-group-item`, `checkbox-control`, `checkbox-label` | Present when the field input is required. |
| `data-checked` | `checkbox-group-item`, `checkbox-control`, `checkbox-indicator` | Present when the item or toggle is checked. |
| `data-indeterminate` | `checkbox-group-item`, `checkbox-control`, `checkbox-indicator` | Present when the checkbox or progress state is indeterminate. |
| `data-unchecked` | `checkbox-group-item`, `checkbox-control` | Present when the checkbox, radio item, or switch is unchecked. |

## Props

Props for the CheckboxGroup component.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| checkedIcon | IconT.Name \| undefined | — | Default checked icon for all items. |
| defaultValue | string[] \| undefined | — | The default value of the input (uncontrolled). |
| disabled | boolean \| undefined | false | Whether the input is disabled. |
| id | string \| undefined | — | The ID of the input element. |
| indeterminateIcon | IconT.Name \| undefined | — | Default indeterminate icon for all items. |
| indicator | 'start' \| 'end' \| 'hidden' \| undefined | — | Default indicator position for all items. |
| items | (string \| {<br>  /** Value of the group item. */<br>  value?: string;<br>  /** Label for the group item. */<br>  label?: JSX.Element;<br>  /** Description for the group item. */<br>  description?: JSX.Element;<br>  /** Whether the item is disabled. */<br>  disabled?: boolean;<br>  /** Whether the item is indeterminate. */<br>  indeterminate?: boolean;<br>  /** Custom checked icon for this item. */<br>  checkedIcon?: IconT.Name;<br>  /** Custom indeterminate icon for this item. */<br>  indeterminateIcon?: IconT.Name;<br>})[] \| undefined | — | Array of items to render in the group. |
| legend | JSX.Element \| undefined | — | Legend for the checkbox group. |
| name | string \| undefined | — | The name of the input element, used for form submission. |
| onValueChange | ((value: string[]) => void) \| undefined | — | Callback when the selected values change. |
| orientation | 'horizontal' \| 'vertical' \| undefined | 'vertical' | Visual layout direction. |
| readOnly | boolean \| undefined | false | Whether the input is read-only. |
| required | boolean \| undefined | false | Whether the input is required. |
| size | 'sm' \| 'md' \| 'lg' \| undefined | 'md' | Visual size of the component. |
| value | string[] \| undefined | — | The current value of the input (controlled). |
| variant | 'card' \| 'table' \| 'list' \| undefined | 'list' | Visual treatment of the component. |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| classes | Classes \| undefined | — | Family slot class defaults for this instance. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |
| styles | Styles \| undefined | — | Family slot style defaults for this instance. |
