---
outline: deep
---

# Segmented control

A single-select switch between a few mutually-exclusive options, with a sliding pill behind the selected 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.

## 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>
```

### Form field

It is form-associated: add `name` and the selected `value` is submitted with the form, like a native radio group. Reset restores the initial selection, and `disabled` opts the control out of submission.

```html
<form class="flex flex-col items-start gap-3">
  <l-segmented-control
    name="billing"
    label="Billing period"
    value="yearly"
  >
    <button value="monthly">Monthly</button>
    <button value="yearly">Yearly</button>
  </l-segmented-control>
  <button
    class="l-button"
    type="reset"
  >
    Reset
  </button>
</form>
```

## Examples

### With icons

Place an `<l-icon>` before the label inside a segment.

```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>
```

### Icon-only

A segment with no text is squared automatically. Give each one an `aria-label`.

```html
<l-segmented-control label="Text alignment">
  <button
    value="left"
    aria-label="Align left"
  >
    <l-icon name="lucide:align-left"></l-icon>
  </button>
  <button
    value="center"
    aria-label="Align center"
  >
    <l-icon name="lucide:align-center"></l-icon>
  </button>
  <button
    value="right"
    aria-label="Align right"
  >
    <l-icon name="lucide:align-right"></l-icon>
  </button>
</l-segmented-control>
```

### Filter toolbar

Segmented controls line up with `.l-button` triggers and `l-select` at the same `size`, so a filter bar stays visually consistent. Here the dropdowns take a quiet neutral fill (`bg-fill-neutral-subtle`) so the chrome recedes and the active selections carry the emphasis.

```html
<div class="flex flex-wrap items-center gap-2">
  <button class="l-button bg-fill-neutral-subtle">
    <l-icon name="lucide:layout-list"></l-icon>
    List view
    <l-icon name="lucide:chevron-down"></l-icon>
  </button>
  <button class="l-button bg-fill-neutral-subtle">
    <l-icon name="lucide:arrow-down-narrow-wide"></l-icon>
    Sort
    <l-icon name="lucide:chevron-down"></l-icon>
  </button>
  <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.
