Installation
import { WrSegmented } from 'ngwr/segmented';
@Component({ imports: [WrSegmented] })
export class MyComponent {}Basic usage
<wr-segmented [options]="options" [(value)]="range" />With icons
<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.
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.
interface WrSegmentedOption<T = unknown> {
value: T;
label?: string;
icon?: WrIconName;
disabled?: boolean;
}| Name | Description | Type | Default |
|---|---|---|---|
WrSegmentedOption | One entry in the track. | interface | — |
valuerequired | 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 |
|---|---|---|---|
optionsrequired | 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 |