# Segmented

> Single-choice picker rendered as a segmented control. A signal-forms native control — it implements `FormValueControl`, so `[formField]` binds straight to its `value` model. `[(value)]` works standalone, and `[(ngModel)]` / reactive forms keep working through Angular's bridge.

Source: https://ngwr.dev/reference/components/segmented  
Kind: Signal Forms, Standalone

## Installation

```angular-ts
import { WrSegmented } from 'ngwr/segmented';

@Component({ imports: [WrSegmented] })
export class MyComponent {}
```

## Basic usage

```html
<wr-segmented [options]="options" [(value)]="range" />
```

## With icons

```html
<wr-segmented
  [options]="[
    { value: 'light', label: 'Light', icon: 'sun' },
    { value: 'dark', label: 'Dark', icon: 'moon' },
  ]"
  [(value)]="mode"
/>
```

## Signal Forms

`[formField]` writes the component's own `value` model — there is no `ControlValueAccessor` in between. Inside a `<wr-form-field>` the label points at the first segment (a `<label for>` may only name a labelable element, and `<wr-segmented>` is not one), while `aria-describedby` and `aria-invalid` land on the group, because the strip is one field rather than a row of them. `touch` fires when focus leaves the strip, which is what lets the field show its copy.

```angular-ts
import { Component, signal } from '@angular/core';
import { FormField, form, required } from '@angular/forms/signals';
import { WrFormField } from 'ngwr/form';
import { WrSegmented, type WrSegmentedOption } from 'ngwr/segmented';

@Component({
  selector: 'app-schedule',
  imports: [FormField, WrFormField, WrSegmented],
  template: `
    <wr-form-field label="Range">
      <wr-segmented [options]="ranges" [formField]="schedule.range" />
    </wr-form-field>
  `,
})
export class ScheduleComponent {
  protected readonly ranges: readonly WrSegmentedOption<string>[] = [
    { value: 'day', label: 'Day' },
    { value: 'week', label: 'Week' },
    { value: 'month', label: 'Month' },
  ];

  private readonly model = signal({ range: '' });
  protected readonly schedule = form(this.model, path => {
    required(path.range);
  });
}
```

## Accessibility

The host stays a `role="group"` of `aria-pressed` toggle buttons. Being a form control changes what the value binds to, not what the widget is — a `radiogroup` would owe a single roving tab stop and arrow-key selection, a different keyboard contract. Each enabled segment is its own tab stop, and Enter / Space activate it as the browser's own default action on a button.

## Types

Data shapes used by the inputs and outputs above.

```angular-ts
interface WrSegmentedOption<T = unknown> {
  value: T;
  label?: string;
  icon?: WrIconName;
  disabled?: boolean;
}
```

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `WrSegmentedOption` | One entry in the track. | `interface` | — |
| `value`required | Model value when this segment is picked. | `T` | — |
| `label` | Visible text; omit for icon-only segments. | `string` | — |
| `icon` | Leading icon. | `WrIconName` | — |
| `disabled` | Disable this segment. | `boolean` | `false` |

## API

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `options`required | The segments to render. | `readonly WrSegmentedOption<T>[]` | — |
| `value` | The picked segment's `value`, `null` when nothing is selected. Bound by `[formField]`, or two-way via `[(value)]`. | `T \| null` | `null` |
| `(touch)` | Emitted when focus leaves the strip, so a bound field can mark itself touched. | `void` | — |
| `disabled` | Disable the whole control. Bound automatically from the field's disabled state when used with `[formField]`. | `boolean` | `false` |
| `readonly` | Refuse changes while every segment stays focusable and the value still submits. Bound automatically from the field's readonly state when used with `[formField]`. NOTHING is mirrored into ARIA here, deliberately: the strip is a `role="group"` of `aria-pressed` buttons, and ARIA defines `aria-readonly` for neither role — it is a state of `checkbox` / `radiogroup` / `textbox` and their kin, and it is not global. `aria-disabled` is the only attribute that would apply to a button, and it says the wrong thing: these segments are still focusable and the value still submits. So the state is honest in the DOM (`wr-segmented--readonly`) and silent in the accessibility tree rather than announced with a word that means something else. | `boolean` | `false` |
| `size` | Control size — shares the `--wr-control-*` contract. | `WrSegmentedSize` | `'md'` |

## CSS variables

Custom properties `ngwr/segmented` publishes. Each default below is declared on the component's own selector, so a `:root` override is shadowed by it — set them on that selector, on a wrapper you scope yourself, or inline on the element. Unlike the BEM class names, these are the supported way to restyle the component.

| Variable | Default | Declared on |
| --- | --- | --- |
| `--wr-segmented-bg` | `var(--wr-color-fill)` | `.wr-segmented` |
| `--wr-segmented-color` | `var(--wr-color-on-surface)` | `.wr-segmented` |
| `--wr-segmented-font-size` | `var(--wr-text-sm)` | `.wr-segmented` +2 variant overrides |
| `--wr-segmented-line-height` | `var(--wr-control-line-height-md)` | `.wr-segmented` +2 variant overrides |
| `--wr-segmented-option-py` | `0.125rem` | `.wr-segmented` +2 variant overrides |
| `--wr-segmented-padding` | `0.1875rem` | `.wr-segmented` +2 variant overrides |
| `--wr-segmented-radius` | `0.5rem` | `.wr-segmented` +2 variant overrides |
| `--wr-segmented-selected-bg` | `var(--wr-color-surface)` | `.wr-segmented` |
| `--wr-segmented-selected-color` | `var(--wr-color-on-surface)` | `.wr-segmented` |
| `--wr-segmented-thumb-count` | `1` | `.wr-segmented` |
| `--wr-segmented-thumb-index` | `0` | `.wr-segmented` |
| `--wr-segmented-thumb-radius` | `calc(var(--wr-segmented-radius) - var(--wr-segmented-padding))` | `.wr-segmented` |
