# Toggle Group

A set of toggles that make up one setting, spaced apart or joined into one control.

Source: [toggle-group.tsx](https://github.com/jakejarvis/jarvis-ui/blob/main/src/components/ui/toggle-group.tsx)

```tsx
import { FieldLabel } from "@/components/ui/field";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";

const days = [
  { value: "mon", label: "Monday" },
  { value: "tue", label: "Tuesday" },
  { value: "wed", label: "Wednesday" },
  { value: "thu", label: "Thursday" },
  { value: "fri", label: "Friday" },
  { value: "sat", label: "Saturday" },
  { value: "sun", label: "Sunday" },
];

export default function ToggleGroupMultiple() {
  return (
    <div className="flex flex-col gap-2">
      <FieldLabel id="repeat-on-label">Repeat on</FieldLabel>
      <ToggleGroup
        multiple
        spacing={1}
        defaultValue={["mon", "wed", "fri"]}
        aria-labelledby="repeat-on-label"
      >
        {days.map((day) => (
          <ToggleGroupItem key={day.value} value={day.value} aria-label={day.label}>
            {day.label[0]}
          </ToggleGroupItem>
        ))}
      </ToggleGroup>
    </div>
  );
}
```

## Installation

Add the registry, once per project:

```bash
npx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json
```

Then add the component:

```bash
npx shadcn@latest add @jarvis/toggle-group
```

## Usage

```tsx
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";

export function NotifyBy() {
  return (
    <ToggleGroup multiple defaultValue={["email"]} aria-label="Notify me by">
      <ToggleGroupItem value="email">Email</ToggleGroupItem>
      <ToggleGroupItem value="push">Push</ToggleGroupItem>
      <ToggleGroupItem value="sms">SMS</ToggleGroupItem>
    </ToggleGroup>
  );
}
```

`multiple` lets any number of items be pressed. The value is always an array. Give the group an
`aria-label` or `aria-labelledby`, and give icon-only items their own labels.

When exactly one option must be selected, use a [Segmented Control](https://ui.jarv.is/components/segmented-control.md).
Inside a [Toolbar](https://ui.jarv.is/components/toolbar.md), use `ToolbarToggleGroup`.

## Composition

- `ToggleGroup`
  - `ToggleGroupItem`

## Examples

### Single

Without `multiple`, only one item can be pressed at a time.

```tsx
import { ThumbsDownIcon, ThumbsUpIcon } from "lucide-react";

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";

export default function ToggleGroupSingle() {
  return (
    <div className="flex items-center gap-3">
      <span id="helpful-label" className="text-sm text-muted-foreground">
        Was this helpful?
      </span>
      <ToggleGroup aria-labelledby="helpful-label">
        <ToggleGroupItem value="yes" aria-label="Yes">
          <ThumbsUpIcon />
        </ToggleGroupItem>
        <ToggleGroupItem value="no" aria-label="No">
          <ThumbsDownIcon />
        </ToggleGroupItem>
      </ToggleGroup>
    </div>
  );
}
```

### Joined

Set `spacing={0}` to join the items into one control.

```tsx
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";

export default function ToggleGroupJoined() {
  return (
    <ToggleGroup
      multiple
      spacing={0}
      size="sm"
      defaultValue={["warn", "error"]}
      aria-label="Log levels"
    >
      <ToggleGroupItem value="debug">Debug</ToggleGroupItem>
      <ToggleGroupItem value="info">Info</ToggleGroupItem>
      <ToggleGroupItem value="warn">Warn</ToggleGroupItem>
      <ToggleGroupItem value="error">Error</ToggleGroupItem>
    </ToggleGroup>
  );
}
```

### In a form

Give a `Field` or the group a `name`, and each pressed item's value submits under it, so
`formData.getAll(name)` returns them all, and `Form`'s `onFormSubmit` gets them as an array. Pass
`form` with a form's `id` when the group sits outside it. With `required`, the form won't submit
until an item is pressed, which fails as `valueMissing`. A form reset brings back `defaultValue`.
Inside a `Field`, a `FieldLabel` names the group, and the group reads out the field's
`FieldDescription` and `FieldError`.

```tsx
import { Button } from "@/components/ui/button";
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import { toast } from "@/components/ui/toast";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";

const days = [
  { value: "mon", label: "Monday" },
  { value: "tue", label: "Tuesday" },
  { value: "wed", label: "Wednesday" },
  { value: "thu", label: "Thursday" },
  { value: "fri", label: "Friday" },
];

export default function ToggleGroupForm() {
  return (
    <Form
      className="max-w-sm"
      onFormSubmit={(values) =>
        toast.add({
          title: "Schedule saved",
          description: `Office days: ${(values.days as string[]).join(", ")}.`,
        })
      }
    >
      <Field name="days">
        <FieldLabel>Office days</FieldLabel>
        <ToggleGroup multiple spacing={1} required>
          {days.map((day) => (
            <ToggleGroupItem key={day.value} value={day.value} aria-label={day.label}>
              {day.label.slice(0, 2)}
            </ToggleGroupItem>
          ))}
        </ToggleGroup>
        <FieldDescription>The days you'll be in this week.</FieldDescription>
        <FieldError match="valueMissing">Choose at least one day.</FieldError>
      </Field>
      <Button type="submit" variant="primary" className="self-start">
        Save schedule
      </Button>
    </Form>
  );
}
```

## Props

`size` on `ToggleGroup` applies to every item.

| Part | Extends |
| --- | --- |
| `ToggleGroup` | [`ToggleGroup`](https://base-ui.com/react/components/toggle-group#api-reference) |
| `ToggleGroupItem` | [`Toggle`](https://base-ui.com/react/components/toggle#api-reference) |

The tables leave out `className`, `style` and `render`. Parts built on Base UI take them as its [styling](https://base-ui.com/react/handbook/styling) and [composition](https://base-ui.com/react/handbook/composition) guides describe.

### ToggleGroup

| Prop             | Type                                                                             | Description                                                                                                                                                                                     |
| ---------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `value?`         | `readonly string[]`                                                              | The pressed state of the toggle group represented by an array of the values of all pressed toggle buttons. This is the controlled counterpart of `defaultValue`.                                |
| `defaultValue?`  | `readonly string[]`                                                              | The pressed state of the toggle group represented by an array of the values of all pressed toggle buttons. This is the uncontrolled counterpart of `value`.                                     |
| `onValueChange?` | `((groupValue: string[], eventDetails: ToggleGroup.ChangeEventDetails) => void)` | Callback fired when the pressed states of the toggle group changes.                                                                                                                             |
| `disabled?`      | `boolean`                                                                        | Whether the toggle group should ignore user interaction. Default: `false`                                                                                                                       |
| `orientation?`   | `"horizontal" \| "vertical"`                                                     | Default: `"horizontal"`                                                                                                                                                                         |
| `loopFocus?`     | `boolean`                                                                        | Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. Default: `true`                                                           |
| `multiple?`      | `boolean`                                                                        | When `false` only one item in the group can be pressed. If any item in the group becomes pressed, the others will become unpressed. When `true` multiple items can be pressed. Default: `false` |
| `size?`          | `"default" \| "lg" \| "sm"`                                                      |                                                                                                                                                                                                 |
| `spacing?`       | `number`                                                                         | Default: `2`                                                                                                                                                                                    |
| `name?`          | `string`                                                                         | Submits each pressed item's value with a form under this name.                                                                                                                                  |
| `form?`          | `string`                                                                         | The `id` of the form to submit the value with, when the group isn't inside it.                                                                                                                  |
| `required?`      | `boolean`                                                                        | Whether a form can't be submitted until at least one item is pressed. Default: `false`                                                                                                          |

### ToggleGroupItem

| Prop               | Type                                                                    | Description                                                                                                                                                                                       |
| ------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pressed?`         | `boolean`                                                               | Whether the toggle button is currently pressed. This is the controlled counterpart of `defaultPressed`.                                                                                           |
| `defaultPressed?`  | `boolean`                                                               | Whether the toggle button is currently pressed. This is the uncontrolled counterpart of `pressed`. Default: `false`                                                                               |
| `disabled?`        | `boolean`                                                               | Whether the component should ignore user interaction. Default: `false`                                                                                                                            |
| `onPressedChange?` | `((pressed: boolean, eventDetails: Toggle.ChangeEventDetails) => void)` | Callback fired when the pressed state is changed.                                                                                                                                                 |
| `value?`           | `string`                                                                | A unique string that identifies the toggle when used inside a toggle group.                                                                                                                       |
| `nativeButton?`    | `boolean`                                                               | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). Default: `true` |
| `size?`            | `"default" \| "lg" \| "sm"`                                             | Default: `"default"`                                                                                                                                                                              |
