OSegmented
OSegmented is a compact group of two to six mutually exclusive choices, such as a Monthly / Yearly billing toggle or a row of list filters. The active option has a filled background, and each option can show a counter.
Usage
Step by step
- Import
OSegmentedfrom theooko-ui/segmentedpublic subpath. - Pass the choices through
optionsand bind the selected value withv-model. - Give the group an accessible name with
aria-labeloraria-labelledby; addcountto an option to show its counter.
Example
The rendered example and its source come from the same Vue single-file component.
Interactive playground
Change the selected option, the number of options, the counters, and the disabled state. The preview updates immediately.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | Value | undefined | Currently selected option value. |
options | readonly OSegmentedOption<Value>[] | undefined | Mutually exclusive options, intended for two to six choices. |
disabled | boolean | false | Prevents every selection change. |
soft | boolean | false | Marks the selected option with a primary tint and primary text instead of a solid fill, for busy filter bars. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | Value | Emits the selected option value after a native selection change. |
Slots
| Slot | Props | Description |
|---|---|---|
| none | none | No slots are exposed. |
Related types
| Type | Definition |
|---|---|
OSegmentedOption | interface OSegmentedOption<Value extends OSegmentedValue = OSegmentedValue> { /** Value emitted when this option is selected. */ value: Value; /** Visible option label. */ label: string; /** Optional counter displayed after the label. */ count?: number; } |
OSegmentedValue | type OSegmentedValue = number | string; |
CSS custom properties
| Custom property | Default | Description |
|---|---|---|
--o-segmented-padding | var(--o-space-0-5) | Sets the space between the track edge and its options. |
--o-segmented-option-padding | var(--o-space-1) var(--o-space-3) | Sets the inner spacing of each option. |
--o-segmented-border-radius | var(--o-radius-round) | Sets the track corner radius; option corners derive from it. |
--o-segmented-focus-width | 0.125rem | Sets the focus outline width. |
--o-segmented-disabled-opacity | 0.5 | Sets the opacity of a disabled control. |
--o-segmented-scroll-fade-width | var(--o-space-8) | Sets the width of the fade on an edge that still hides options. |
Override example
:root {
--o-segmented-border-radius: var(--o-radius-round);
--o-segmented-option-padding: var(--o-space-1-5) var(--o-space-4);
}Accessibility
The root has the radiogroup role and receives native attributes such as aria-label or aria-labelledby, which name the group. Each option is a native radio inside its label, so the option text and its counter form the accessible name. The group is a single tab stop; arrow keys move the selection between options, and the focused option shows a visible outline. disabled disables every radio natively and sets aria-disabled on the group. The selected state does not rely on color alone in forced-colors mode, which uses system highlight colors.
Behavior and edge cases
modelValue is controlled: selecting an option emits update:modelValue with its value, and the parent updates the binding. Values are strings or numbers and are compared with Object.is; a value that matches no option leaves every option unselected. A counter of 0 is displayed, while an option without count shows none. The radios share an SSR-stable generated name, so several groups on a page stay independent. soft marks the selected option with a primary tint and primary text instead of a solid fill, which suits busy filter bars. When the options do not fit their container, such as on a narrow screen, the track scrolls horizontally instead of overflowing the page: an edge that still hides options fades out over --o-segmented-scroll-fade-width, and the selected option is scrolled into view on mount and whenever modelValue changes. OSegmented exposes no app defaults. The packer combines component CSS in style.css; component-level CSS isolation is not claimed.