# Checkbox Group

Shares selection state across a set of checkboxes.

```tsx
import { Checkbox } from "@/components/ui/checkbox"
import { CheckboxGroup } from "@/components/ui/checkbox-group"
import { Field, FieldItem, FieldLabel } from "@/components/ui/field"
export default function CheckboxGroupDemo() {
  return (
    <Field>
      <FieldLabel nativeLabel={false} render={<div />}>
        Apples
      </FieldLabel>
      <CheckboxGroup defaultValue={["fuji"]}>
        {[
          ["fuji", "Fuji"],
          ["gala", "Gala"],
          ["granny-smith", "Granny Smith"],
        ].map(([value, label]) => (
          <FieldItem key={value}>
            <FieldLabel>
              <Checkbox value={value} />
              {label}
            </FieldLabel>
          </FieldItem>
        ))}
      </CheckboxGroup>
    </Field>
  )
}
```

## Installation

1. Install the following dependencies:

   ```bash
   npm install @base-ui/react @stylexjs/stylex
   ```

2. Set up [Installation](https://carbon.style/docs/installation), including `state-styles.ts`. Install [Checkbox](https://carbon.style/docs/components/checkbox) for the choices and [Field](https://carbon.style/docs/components/field) for labels and validation.

3. Copy and paste the following code into your project.

   ```tsx title="components/ui/checkbox-group.tsx"
   "use client"

   import type { CSSProperties } from "react"
   import { CheckboxGroup as CheckboxGroupPrimitive } from "@base-ui/react/checkbox-group"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"
   import { stateStyles } from "@/lib/state-styles"
   import { space } from "@/theme/tokens.stylex"

   const styles = stylex.create({ root: { display: "flex", flexDirection: "column", gap: space.s3 } })
   const checkboxGroupStyles = { root: styles.root }

   function CheckboxGroup({
     xstyle,
     className,
     style,
     ...props
   }: Omit<CheckboxGroupPrimitive.Props, "className" | "style"> & {
     xstyle?: StyleXStyles
     className?: string
     style?: CSSProperties
   }) {
     return (
       <CheckboxGroupPrimitive
         data-slot="checkbox-group"
         {...props}
         {...stateStyles(() => [styles.root, xstyle], { className, style })}
       />
     )
   }

   export { CheckboxGroup, checkboxGroupStyles }
   ```

4. Update the import paths to match your project setup.

## Usage

```tsx
import { CheckboxGroup } from "@/components/ui/checkbox-group"
import { Checkbox } from "@/components/ui/checkbox"
import { Field, FieldItem, FieldLabel } from "@/components/ui/field"
```

```tsx
<Field name="fruit">
  <FieldLabel nativeLabel={false} render={<div />}>
    Fruit
  </FieldLabel>
  <CheckboxGroup defaultValue={["apple"]}>
    <FieldItem>
      <FieldLabel>
        <Checkbox value="apple" />
        Apple
      </FieldLabel>
    </FieldItem>
    <FieldItem>
      <FieldLabel>
        <Checkbox value="pear" />
        Pear
      </FieldLabel>
    </FieldItem>
  </CheckboxGroup>
</Field>
```

Set each checkbox's `value` and pass selected values to the group. Put `name` on `Field` for form submission.

## Labeling a checkbox group

Use `FieldLabel` for the group caption and `FieldItem` to associate each option with its own label.

```tsx
import { Checkbox } from "@/components/ui/checkbox"
import { CheckboxGroup } from "@/components/ui/checkbox-group"
import { Field, FieldDescription, FieldItem, FieldLabel } from "@/components/ui/field"
const items = [
  { value: "hard-disks", label: "Hard disks" },
  { value: "external-disks", label: "External disks" },
  { value: "cds-dvds", label: "CDs, DVDs, and iPods" },
  { value: "connected-servers", label: "Connected servers" },
]
export default function CheckboxGroupExample() {
  return (
    <Field name="desktop-items">
      <FieldLabel nativeLabel={false} render={<div />}>
        Show these items on the desktop:
      </FieldLabel>
      <FieldDescription>Select the items you want to show on the desktop.</FieldDescription>
      <CheckboxGroup defaultValue={["hard-disks", "external-disks"]}>
        {items.map(({ value, label }) => (
          <FieldItem key={value}>
            <FieldLabel>
              <Checkbox value={value} />
              {label}
            </FieldLabel>
          </FieldItem>
        ))}
      </CheckboxGroup>
    </Field>
  )
}
```

## Rendering as a native button

Use `nativeButton` and `render={<button />}` when placing the label beside its checkbox.

```tsx
import { Checkbox } from "@/components/ui/checkbox"
import { CheckboxGroup } from "@/components/ui/checkbox-group"
import { Field, FieldItem, FieldLabel } from "@/components/ui/field"
import { HStack } from "@/components/ui/stack"
export default function CheckboxGroupNativeButton() {
  return (
    <Field>
      <FieldLabel nativeLabel={false} render={<div />}>
        Allowed network protocols
      </FieldLabel>
      <CheckboxGroup defaultValue={["https"]}>
        {["http", "https", "ssh"].map((value) => (
          <FieldItem key={value}>
            <HStack gap={2}>
              <Checkbox
                value={value}
                nativeButton
                render={<button aria-label={value.toUpperCase()} />}
              />
              <FieldLabel>{value.toUpperCase()}</FieldLabel>
            </HStack>
          </FieldItem>
        ))}
      </CheckboxGroup>
    </Field>
  )
}
```

## Form integration

Validate the selected array with `Field` and display its message with `FieldError`.

```tsx
import { useState } from "react"
import { Checkbox } from "@/components/ui/checkbox"
import { CheckboxGroup } from "@/components/ui/checkbox-group"
import { Field, FieldDescription, FieldError, FieldItem, FieldLabel } from "@/components/ui/field"
import { Form } from "@/components/ui/form"
import { Button } from "@/components/ui/button"
export default function CheckboxGroupForm() {
  const [submitted, setSubmitted] = useState<string[] | null>(null)
  return (
    <Form onFormSubmit={(values) => setSubmitted(values.protocols as string[])}>
      <Field
        name="protocols"
        validate={(value) =>
          Array.isArray(value) && value.length > 0 ? null : "Select at least one protocol."
        }
      >
        <FieldLabel nativeLabel={false} render={<div />}>
          Allowed network protocols
        </FieldLabel>
        <FieldDescription>Select one or more protocols.</FieldDescription>
        <CheckboxGroup>
          {["http", "https", "ssh"].map((value) => (
            <FieldItem key={value}>
              <FieldLabel>
                <Checkbox value={value} />
                {value.toUpperCase()}
              </FieldLabel>
            </FieldItem>
          ))}
        </CheckboxGroup>
        <FieldError />
      </Field>
      <Button type="submit">Save protocols</Button>
      {submitted && <output>Saved: {submitted.join(", ")}</output>}
    </Form>
  )
}
```

## Parent checkbox

Use `parent` on a checkbox and pass `allValues`, `value`, and `onValueChange` to the group.

```tsx
import * as stylex from "@stylexjs/stylex"
import { VStack } from "@/components/ui/stack"
import { space } from "@/theme/tokens.stylex"
import { useState } from "react"
import { Checkbox } from "@/components/ui/checkbox"
import { CheckboxGroup } from "@/components/ui/checkbox-group"
import { Field, FieldItem, FieldLabel } from "@/components/ui/field"
const styles = stylex.create({ children: { marginInlineStart: space.s4 } })
const values = ["fuji", "gala", "granny-smith"]
export default function CheckboxGroupParent() {
  const [value, setValue] = useState(["fuji"])
  return (
    <Field>
      <CheckboxGroup aria-label="Apples" allValues={values} value={value} onValueChange={setValue}>
        <FieldItem>
          <FieldLabel>
            <Checkbox parent />
            Apples
          </FieldLabel>
        </FieldItem>
        <VStack gap={3} xstyle={styles.children}>
          {[
            ["fuji", "Fuji"],
            ["gala", "Gala"],
            ["granny-smith", "Granny Smith"],
          ].map(([value, label]) => (
            <FieldItem key={value}>
              <FieldLabel>
                <Checkbox value={value} />
                {label}
              </FieldLabel>
            </FieldItem>
          ))}
        </VStack>
      </CheckboxGroup>
    </Field>
  )
}
```

## Nested parent checkbox

Keep the nested group's values in the outer selection to update both parents together.

```tsx
import { useId, useState } from "react"
import * as stylex from "@stylexjs/stylex"
import { Checkbox } from "@/components/ui/checkbox"
import { CheckboxGroup } from "@/components/ui/checkbox-group"
import { Field, FieldItem, FieldLabel } from "@/components/ui/field"
import { VStack } from "@/components/ui/stack"
import { space } from "@/theme/tokens.stylex"
const management = ["create-user", "edit-user", "delete-user", "assign-roles"]
const permissions = ["view-dashboard", "access-reports", ...management]
const labels: Record<string, string> = {
  "view-dashboard": "View Dashboard",
  "access-reports": "Access Reports",
  "create-user": "Create User",
  "edit-user": "Edit User",
  "delete-user": "Delete User",
  "assign-roles": "Assign Roles",
}
const styles = stylex.create({ nested: { marginInlineStart: space.s4 } })
export default function CheckboxGroupNested() {
  const controlsId = useId()
  const [value, setValue] = useState<string[]>([])
  return (
    <Field>
      <CheckboxGroup
        aria-label="User Permissions"
        allValues={permissions}
        value={value}
        onValueChange={setValue}
      >
        <FieldItem>
          <FieldLabel>
            <Checkbox parent render={(props) => <span {...props} aria-controls={controlsId} />} />
            User Permissions
          </FieldLabel>
        </FieldItem>
        <VStack id={controlsId} gap={3} xstyle={styles.nested}>
          {["view-dashboard", "access-reports"].map((permission) => (
            <FieldItem key={permission}>
              <FieldLabel>
                <Checkbox value={permission} />
                {labels[permission]}
              </FieldLabel>
            </FieldItem>
          ))}
          <CheckboxGroup
            aria-label="Manage Users"
            allValues={management}
            value={value.filter((v) => management.includes(v))}
            onValueChange={(next) =>
              setValue((current) => [...current.filter((v) => !management.includes(v)), ...next])
            }
          >
            <FieldItem>
              <FieldLabel>
                <Checkbox parent />
                Manage Users
              </FieldLabel>
            </FieldItem>
            <VStack gap={3} xstyle={styles.nested}>
              {management.map((permission) => (
                <FieldItem key={permission}>
                  <FieldLabel>
                    <Checkbox value={permission} />
                    {labels[permission]}
                  </FieldLabel>
                </FieldItem>
              ))}
            </VStack>
          </CheckboxGroup>
        </VStack>
      </CheckboxGroup>
    </Field>
  )
}
```

## Controlled

Use `value` and `onValueChange` for controlled selection; use `FieldItem disabled` for an unavailable option.

```tsx
import { useState } from "react"
import { Checkbox } from "@/components/ui/checkbox"
import { CheckboxGroup } from "@/components/ui/checkbox-group"
import { Field, FieldDescription, FieldItem, FieldLabel } from "@/components/ui/field"
import { HStack } from "@/components/ui/stack"
export default function CheckboxGroupControlled() {
  const [value, setValue] = useState(["email"])
  return (
    <Field>
      <FieldLabel nativeLabel={false} render={<div />}>
        Notifications
      </FieldLabel>
      <CheckboxGroup value={value} onValueChange={setValue}>
        {["email", "sms", "push"].map((option) => (
          <FieldItem key={option} disabled={option === "push"}>
            <HStack gap={2}>
              <Checkbox value={option} nativeButton render={<button aria-label={option} />} />
              <FieldLabel>{option}</FieldLabel>
            </HStack>
          </FieldItem>
        ))}
      </CheckboxGroup>
      <FieldDescription>Selected: {value.join(", ") || "none"}</FieldDescription>
    </Field>
  )
}
```

## Disabled and invalid

Set `disabled` or `invalid` on `Field` to style every checkbox in the group.

```tsx
import { Checkbox } from "@/components/ui/checkbox"
import { CheckboxGroup } from "@/components/ui/checkbox-group"
import { Field, FieldError, FieldItem, FieldLabel } from "@/components/ui/field"
import { HStack, VStack } from "@/components/ui/stack"
export default function CheckboxGroupDisabled() {
  return (
    <VStack gap={6}>
      {(["disabled", "invalid", "disabled-invalid"] as const).map((state) => (
        <Field
          key={state}
          disabled={state.includes("disabled")}
          invalid={state.includes("invalid")}
        >
          <FieldLabel nativeLabel={false} render={<div />}>
            {state}
          </FieldLabel>
          <CheckboxGroup defaultValue={["email"]}>
            <FieldItem>
              <HStack gap={2}>
                <Checkbox value="email" nativeButton render={<button aria-label="Email" />} />
                <FieldLabel>Email</FieldLabel>
              </HStack>
            </FieldItem>
            <FieldItem>
              <HStack gap={2}>
                <Checkbox value="sms" nativeButton render={<button aria-label="SMS" />} />
                <FieldLabel>SMS</FieldLabel>
              </HStack>
            </FieldItem>
          </CheckboxGroup>
          {state.includes("invalid") && <FieldError>Select a notification method.</FieldError>}
        </Field>
      ))}
    </VStack>
  )
}
```

## API reference

`CheckboxGroup` accepts [Base UI Checkbox Group props](https://base-ui.com/react/components/checkbox-group), including `render`, plus `xstyle`, `className`, and inline `style`.

### checkboxGroupStyles

| Key    | What it is                                 |
| ------ | ------------------------------------------ |
| `root` | Vertical layout with space between choices |

### Data attributes

| Attribute                                                                                 | On   | Effect         |
| ----------------------------------------------------------------------------------------- | ---- | -------------- |
| `data-slot="checkbox-group"`                                                              | root | Group anatomy  |
| `data-disabled`                                                                           | root | Disabled state |
| `data-valid`, `data-invalid`, `data-touched`, `data-dirty`, `data-filled`, `data-focused` | root | Field state    |

See [Checkbox](https://carbon.style/docs/components/checkbox) for individual control attributes.
