<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# SegmentedControl

An iOS-style segmented control: a row of equal segments with a raised thumb that slides to the checked one, for switching how one view shows its content in place, such as list or grid. Exactly one segment is checked. Screen readers hear a radio group, and the arrow keys check the next segment as they do for radio buttons. Segments are 44px tall on touch screens, and `name` submits the value with a form.

```tsx
import { SegmentedControl } from "@/components/ouro/segmented-control/segmented-control";
```

## Example

```tsx
<SegmentedControl
  label="View"
  hideLabel
  items={[
    { value: "list", label: "List" },
    { value: "grid", label: "Grid" },
  ]}
  value={view}
  onValueChange={setView}
/>
```

## Use instead when

- Use `Tabs` instead when each choice shows different content with its own panel.
- Use links in a `<nav>` instead when each choice is its own page.
- Use a `RadioGroup` instead for a form value that is submitted.

## Props

Type: `SegmentedControlProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  | Layout only: margins, grid placement (`mt-4`). Appearance classes warn in development. |
| `defaultValue` | `string` | the first enabled item | The segment pressed first, when the component keeps its own state. |
| `disabled` | `boolean` |  | Disables every segment. |
| `fullWidth` | `boolean` |  | Stretches the control to the width of its container, with the segments sharing it equally. |
| `hideLabel` | `boolean` |  | Hides the label visually; it still names the control for screen readers. Set it on a view switcher whose segments say what it is. |
| `items` (required) | `readonly SegmentedControlItem[]` |  | The segments, in order. Keep to two to five. |
| `label` (required) | `string` |  | Names the choice ("View"). Shown above the control unless `hideLabel` is set; screen readers always hear it. |
| `name` | `string` |  | Submits the checked segment's value under this name with the form it sits in, unless the control is `disabled`. |
| `onValueChange` | `((value: string) => void)` |  | Called with the new segment's value when the person presses a segment. Pressing the pressed one does nothing. |
| `ref` | `Ref<HTMLDivElement>` |  | Reaches the group element (`role="radiogroup"`). |
| `value` | `string` |  | The pressed segment's value, when you control it. Pair it with `onValueChange`. |

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `onChange`: Not a prop. Ouro UI uses `onValueChange`, which gets the value: `<SegmentedControl onValueChange={(value) => …}>`.
- `options`: Not a prop. Ouro UI uses `items`: `<SegmentedControl items={[{ value, label }]}>`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: SegmentedControl merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `SegmentedControlItem`

One segment.

```ts
interface SegmentedControlItem {
  /** Identifies the segment: `value`, `defaultValue` and `onValueChange` use it. Unique within the control. */
  value: string;
  /** The segment's text. Keep it to one short word; every segment gets the width of the longest. */
  label: string;
  /** Shows the segment but keeps it from being pressed. */
  disabled?: boolean | undefined;
  /** Not a prop. A segment switches a view in place; to go to another page, use `Tabs` or links in a `<nav>`. */
  href?: { 'A segment switches a view in place. To navigate, use links in a <nav>': never };
}
```

[^layout-classes]: test:registry/ouro/components/ouro/segmented-control/segmented-control.test.tsx::SegmentedControl > lets app layout classes win verify:testExists(registry/ouro/components/ouro/segmented-control/segmented-control.test.tsx::SegmentedControl > lets app layout classes win)
