---
outline: deep
---

# Segmented control

A hybrid between a button group, radio buttons, and tabs: pick one of a few closely related options or views, and **the selection applies immediately**. A sliding pill marks the active segment.

**`<l-segmented-control>`** — Progressive Custom Element

```html
<l-segmented-control
  label="View"
  value="list"
>
  <button value="list">List</button>
  <button value="board">Board</button>
  <button value="calendar">Calendar</button>
</l-segmented-control>
```

Each segment is a native `<button>`. Set `value` on the control to the `value` of the initially-selected segment; the control emits a `change` event with the new `value` when the selection changes.

The immediate effect is the point: act on `change` and switch the view or re-run the query right away. For a value the user confirms later by submitting a form, use a [radio group](./radio-group.md) instead — same look, native radios, so arrow-key selection, submission, reset and validation come from the platform.

## Options

### Label

Add `label` to give the control an accessible name announced by screen readers.

```html
<l-segmented-control
  label="View"
  value="list"
>
  <button value="list">List</button>
  <button value="board">Board</button>
  <button value="calendar">Calendar</button>
</l-segmented-control>
```

### Sizes

Add `size` (`sm`, `lg`, `xl`) to align the control with buttons and form controls of the same size. Defaults to `md`.

```html
<div class="flex flex-col items-start gap-3">
  <l-segmented-control
    label="Size sm"
    size="sm"
  >
    <button value="day">Day</button>
    <button value="week">Week</button>
    <button value="month">Month</button>
  </l-segmented-control>
  <l-segmented-control label="Size md">
    <button value="day">Day</button>
    <button value="week">Week</button>
    <button value="month">Month</button>
  </l-segmented-control>
  <l-segmented-control
    label="Size lg"
    size="lg"
  >
    <button value="day">Day</button>
    <button value="week">Week</button>
    <button value="month">Month</button>
  </l-segmented-control>
</div>
```

### Full width

Add `full-width` to stretch the segments to fill the container.

```html
<l-segmented-control
  label="Billing period"
  value="yearly"
  full-width
  class="w-full"
>
  <button value="monthly">Monthly</button>
  <button value="yearly">Yearly</button>
</l-segmented-control>
```

## Examples

### With icons

Place an `<l-icon>` before the label inside a segment. A segment with no text at all is squared automatically — give each one an `aria-label`.

```html
<l-segmented-control label="Source">
  <button value="github">
    <l-icon name="lucide:github"></l-icon>
    GitHub
  </button>
  <button value="dribbble">
    <l-icon name="lucide:dribbble"></l-icon>
    Dribbble
  </button>
  <button value="youtube">
    <l-icon name="lucide:youtube"></l-icon>
    YouTube
  </button>
</l-segmented-control>
```

### Filter toolbar

Several controls can share one filter bar. Give each its own `label` so assistive tech announces which filter a segment belongs to, and keep them at the same `size` so the bar stays even. A segment can hold any inline content — here a colour swatch before the label.

```html
<div class="flex flex-wrap items-center gap-2">
  <l-segmented-control
    label="Metric"
    value="cost"
  >
    <button value="volume">Volume</button>
    <button value="cost">Cost</button>
  </l-segmented-control>
  <l-segmented-control
    label="Color"
    value="all"
  >
    <button value="all">All</button>
    <button value="color">
      <span class="size-2.5 rounded-[3px] bg-[#ef4444]"></span>
      Color
    </button>
    <button value="black">
      <span class="size-2.5 rounded-[3px] bg-[#64748b]"></span>
      Black
    </button>
  </l-segmented-control>
</div>
```

## Accessibility

### Criteria

- **Role** — Host gets `role="radiogroup"`; each segment gets `role="radio"` with `aria-checked`
- **Accessible name** — `label` sets `aria-label` so assistive tech announces the control purpose
- **Keyboard** — Arrow keys move selection through the segments; `Home`/`End` jump to the ends
- **Focus order** — Roving `tabindex`: the group is a single `Tab` stop, arrows move within it
- **Icon-only segments** — A segment with no visible text needs its own `aria-label`

### Rules

- Always add a `label` describing the control purpose
- Segments must be native `<button>` elements with a `value`
- Give each icon-only segment its own `aria-label`

### Keyboard interactions

- `Tab` — Moves focus into the control (to the selected segment)
- `ArrowRight` — Selects and focuses the next segment, wrapping
- `ArrowLeft` — Selects and focuses the previous segment, wrapping
- `ArrowDown` — Same as ArrowRight
- `ArrowUp` — Same as ArrowLeft
- `Home` — Selects and focuses the first segment
- `End` — Selects and focuses the last segment

## API reference

### Importing

```js
import 'luxen-ui/segmented-control';
```

```css
@import 'luxen-ui/css/segmented-control';
```

### Attributes & Properties

- **value**: `string` — Value of the selected segment (its `value` attribute, else its index).
- **size**: `SegmentedControlSize` (default: `'md'`) — Control height, aligned with buttons and form controls at the same size.
- **label**: `string | undefined` — Accessible label announced for the group. Not displayed on screen.
- **full-width**: `boolean` (default: `false`) — Stretch segments to fill the container width.

### Events

- **change** — Fired when the selected segment changes. Not cancelable. Bubbles. Properties: `value: string`, `index: number`.

### CSS custom properties

- `--indicator-color` (default: `var(--l-color-surface)`) — Background of the sliding pill behind the selected segment.
- `--border-radius` (default: `var(--l-radius-lg)`) — Corner radius of the track.
