560 lines
18 KiB
Markdown
560 lines
18 KiB
Markdown
# Segmented Control
|
|
|
|
A linear set of two or more segments, each of which functions as a radio input, and a default value is required.
|
|
|
|
## Import
|
|
|
|
```
|
|
Copyts
|
|
import { SegmentedControl } from "@kobalte/core/segmented-control";
|
|
// or
|
|
import { Root, Label, ... } from "@kobalte/core/segmented-control";
|
|
```
|
|
|
|
```
|
|
Copyts
|
|
import { SegmentedControl } from "@kobalte/core/segmented-control";
|
|
// or
|
|
import { Root, Label, ... } from "@kobalte/core/segmented-control";
|
|
```
|
|
|
|
## Features
|
|
|
|
- Follow the [WAI ARIA Radio Group](https://www.w3.org/WAI/ARIA/apg/patterns/radiobutton/) design pattern.
|
|
- Each segment is built with a native HTML `<input>` element, which is visually hidden to allow custom styling.
|
|
- Syncs with form reset events.
|
|
- Group and segment labeling support for assistive technology.
|
|
- Can be controlled or uncontrolled.
|
|
- Supports animatable indicator.
|
|
|
|
## Anatomy
|
|
|
|
The segmented control consists of:
|
|
|
|
- **SegmentedControl**: The root container for the segmented control.
|
|
- **SegmentedControl.Label**: The label that gives the user information on the segmented control.
|
|
- **SegmentedControl.Description**: The description that gives the user more information on the segmented control.
|
|
- **SegmentedControl.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the segmented control.
|
|
- **SegmentedControl.Indicator:** The visual indicator displayed below the items to indicate the selected item.
|
|
|
|
The segmented control item consists of:
|
|
|
|
- **SegmentedControl.Item**: The root container for an item's radio button.
|
|
- **SegmentedControl.ItemInput**: The native html input that is visually hidden in the item's radio button.
|
|
- **SegmentedControl.ItemControl**: The element that visually represents an item's radio button.
|
|
- **SegmentedControl.ItemIndicator**: The visual indicator rendered when the item's radio button is in a checked state.
|
|
- **SegmentedControl.ItemLabel**: The label that gives the user information on the item's radio button.
|
|
- **SegmentedControl.ItemDescription**: The description that gives the user more information on the item's radio button.
|
|
|
|
```
|
|
Copytsx
|
|
<SegmentedControl>
|
|
<SegmentedControl.Label />
|
|
<SegmentedControl.Indicator />
|
|
<SegmentedControl.Item>
|
|
<SegmentedControl.ItemInput />
|
|
<SegmentedControl.ItemControl>
|
|
<SegmentedControl.ItemIndicator />
|
|
</SegmentedControl.ItemControl>
|
|
<SegmentedControl.ItemLabel />
|
|
<SegmentedControl.ItemDescription />
|
|
</SegmentedControl.Item>
|
|
<SegmentedControl.Description />
|
|
<SegmentedControl.ErrorMessage />
|
|
</SegmentedControl>
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
<SegmentedControl>
|
|
<SegmentedControl.Label />
|
|
<SegmentedControl.Indicator />
|
|
<SegmentedControl.Item>
|
|
<SegmentedControl.ItemInput />
|
|
<SegmentedControl.ItemControl>
|
|
<SegmentedControl.ItemIndicator />
|
|
</SegmentedControl.ItemControl>
|
|
<SegmentedControl.ItemLabel />
|
|
<SegmentedControl.ItemDescription />
|
|
</SegmentedControl.Item>
|
|
<SegmentedControl.Description />
|
|
<SegmentedControl.ErrorMessage />
|
|
</SegmentedControl>
|
|
```
|
|
|
|
## Example
|
|
|
|
Favorite fruit
|
|
|
|
Apple
|
|
|
|
Orange
|
|
|
|
Watermelon
|
|
|
|
index.tsxstyle.css
|
|
|
|
```
|
|
Copytsx
|
|
import { SegmentedControl } from "@kobalte/core/segmented-control";
|
|
|
|
import "./style.css";
|
|
|
|
function App() {
|
|
return (
|
|
<SegmentedControl class={style["segmented-control"]} defaultValue="Apple">
|
|
<SegmentedControl.Label class={style["segmented-control__label"]}>
|
|
Favorite fruit
|
|
</SegmentedControl.Label>
|
|
<div class={style["segmented-control__wrapper"]} role="presentation">
|
|
<SegmentedControl.Indicator class={style["segmented-control__indicator"]} />
|
|
<div class={style["segmented-control__items"]} role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{(fruit) => (
|
|
<SegmentedControl.Item value={fruit} class={style["segmented-control__item"]}>
|
|
<SegmentedControl.ItemInput class={style["segmented-control__item-input"]} />
|
|
<SegmentedControl.ItemLabel class={style["segmented-control__item-label"]}>
|
|
{fruit}
|
|
</SegmentedControl.ItemLabel>
|
|
</SegmentedControl.Item>
|
|
)}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
</SegmentedControl>
|
|
);
|
|
}
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
import { SegmentedControl } from "@kobalte/core/segmented-control";
|
|
|
|
import "./style.css";
|
|
|
|
function App() {
|
|
return (
|
|
<SegmentedControl class={style["segmented-control"]} defaultValue="Apple">
|
|
<SegmentedControl.Label class={style["segmented-control__label"]}>
|
|
Favorite fruit
|
|
</SegmentedControl.Label>
|
|
<div class={style["segmented-control__wrapper"]} role="presentation">
|
|
<SegmentedControl.Indicator class={style["segmented-control__indicator"]} />
|
|
<div class={style["segmented-control__items"]} role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{(fruit) => (
|
|
<SegmentedControl.Item value={fruit} class={style["segmented-control__item"]}>
|
|
<SegmentedControl.ItemInput class={style["segmented-control__item-input"]} />
|
|
<SegmentedControl.ItemLabel class={style["segmented-control__item-label"]}>
|
|
{fruit}
|
|
</SegmentedControl.ItemLabel>
|
|
</SegmentedControl.Item>
|
|
)}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
</SegmentedControl>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Usage
|
|
|
|
The `role="presentation"` is required for all non content elements between the `SegmentedControl` and `SegmentedControl.Item` due to a bug in Chromium based browsers that incorrectly parse semantics and break screen readers.
|
|
|
|
### Controlled value
|
|
|
|
The `value` prop, which accepts a value corresponding with the `value` prop of each radio, can be used to make the value controlled. The `onChange` event is fired when the user selects a radio, and receives the new value.
|
|
|
|
Favorite fruit
|
|
|
|
Apple
|
|
|
|
Orange
|
|
|
|
Watermelon
|
|
|
|
Your favorite fruit is: Orange.
|
|
|
|
```
|
|
Copytsx
|
|
import { createSignal } from "solid-js";
|
|
|
|
function ControlledExample() {
|
|
const [value, setValue] = createSignal("Orange");
|
|
|
|
return (
|
|
<>
|
|
<SegmentedControl value={value()} onChange={setValue}>
|
|
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
|
|
<div role="presentation">
|
|
<SegmentedControl.Indicator />
|
|
<div role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{fruit => (
|
|
<SegmentedControl.Item value={fruit}>
|
|
<SegmentedControl.ItemInput />
|
|
<SegmentedControl.ItemLabel>{fruit}</SegmentedControl.ItemLabel>
|
|
</SegmentedControl.Item>
|
|
)}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
</SegmentedControl>
|
|
<p class="not-prose text-sm mt-4">Your favorite fruit is: {value()}.</p>
|
|
</>
|
|
);
|
|
}
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
import { createSignal } from "solid-js";
|
|
|
|
function ControlledExample() {
|
|
const [value, setValue] = createSignal("Orange");
|
|
|
|
return (
|
|
<>
|
|
<SegmentedControl value={value()} onChange={setValue}>
|
|
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
|
|
<div role="presentation">
|
|
<SegmentedControl.Indicator />
|
|
<div role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{fruit => (
|
|
<SegmentedControl.Item value={fruit}>
|
|
<SegmentedControl.ItemInput />
|
|
<SegmentedControl.ItemLabel>{fruit}</SegmentedControl.ItemLabel>
|
|
</SegmentedControl.Item>
|
|
)}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
</SegmentedControl>
|
|
<p class="not-prose text-sm mt-4">Your favorite fruit is: {value()}.</p>
|
|
</>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Description
|
|
|
|
The `SegmentedControl.Description` component can be used to associate additional help text with a segmented control.
|
|
|
|
Favorite fruit
|
|
|
|
Apple
|
|
|
|
Orange
|
|
|
|
Watermelon
|
|
|
|
Choose the fruit you like the most.
|
|
|
|
```
|
|
Copytsx
|
|
<SegmentedControl defaultValue="Apple">
|
|
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
|
|
<div role="presentation">
|
|
<SegmentedControl.Indicator />
|
|
<div role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
<SegmentedControl.Description>Choose the fruit you like the most.</SegmentedControl.Description>
|
|
</SegmentedControl>
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
<SegmentedControl defaultValue="Apple">
|
|
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
|
|
<div role="presentation">
|
|
<SegmentedControl.Indicator />
|
|
<div role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
<SegmentedControl.Description>Choose the fruit you like the most.</SegmentedControl.Description>
|
|
</SegmentedControl>
|
|
```
|
|
|
|
### Error message
|
|
|
|
The `SegmentedControl.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the segmented control as invalid for assistive technologies.
|
|
|
|
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
|
|
|
|
Favorite fruit
|
|
|
|
Apple
|
|
|
|
Orange
|
|
|
|
Watermelon
|
|
|
|
Hmm, I prefer apples.
|
|
|
|
```
|
|
Copytsx
|
|
import { createSignal } from "solid-js";
|
|
|
|
function ErrorMessageExample() {
|
|
const [value, setValue] = createSignal("Orange");
|
|
|
|
return (
|
|
<SegmentedControl
|
|
value={value()}
|
|
onChange={setValue}
|
|
validationState={value() !== "Apple" ? "invalid" : "valid"}
|
|
>
|
|
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
|
|
<div role="presentation">
|
|
<SegmentedControl.Indicator />
|
|
<div role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
<SegmentedControl.ErrorMessage>Hmm, I prefer apples.</SegmentedControl.ErrorMessage>
|
|
</SegmentedControl>
|
|
);
|
|
}
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
import { createSignal } from "solid-js";
|
|
|
|
function ErrorMessageExample() {
|
|
const [value, setValue] = createSignal("Orange");
|
|
|
|
return (
|
|
<SegmentedControl
|
|
value={value()}
|
|
onChange={setValue}
|
|
validationState={value() !== "Apple" ? "invalid" : "valid"}
|
|
>
|
|
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
|
|
<div role="presentation">
|
|
<SegmentedControl.Indicator />
|
|
<div role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
<SegmentedControl.ErrorMessage>Hmm, I prefer apples.</SegmentedControl.ErrorMessage>
|
|
</SegmentedControl>
|
|
);
|
|
}
|
|
```
|
|
|
|
### HTML forms
|
|
|
|
The segmented control `name` prop, paired with the item's radio `value` prop, can be used for integration with HTML forms.
|
|
|
|
Favorite fruit
|
|
|
|
Apple
|
|
|
|
Orange
|
|
|
|
Watermelon
|
|
|
|
ResetSubmit
|
|
|
|
```
|
|
Copytsx
|
|
function HTMLFormExample() {
|
|
const onSubmit = (e: SubmitEvent) => {
|
|
// handle form submission.
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={onSubmit}>
|
|
<SegmentedControl name="favorite-fruit" defaultValue="Orange">
|
|
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
|
|
<div role="presentation">
|
|
<SegmentedControl.Indicator />
|
|
<div role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
</SegmentedControl>
|
|
<div>
|
|
<button type="reset">Reset</button>
|
|
<button type="submit">Submit</button>
|
|
</div>
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
function HTMLFormExample() {
|
|
const onSubmit = (e: SubmitEvent) => {
|
|
// handle form submission.
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={onSubmit}>
|
|
<SegmentedControl name="favorite-fruit" defaultValue="Orange">
|
|
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
|
|
<div role="presentation">
|
|
<SegmentedControl.Indicator />
|
|
<div role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
</SegmentedControl>
|
|
<div>
|
|
<button type="reset">Reset</button>
|
|
<button type="submit">Submit</button>
|
|
</div>
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Orientation
|
|
|
|
The segmented control `orientation` prop can be used to change the segmented control's orientation.
|
|
|
|
Favorite fruit
|
|
|
|
Apple
|
|
|
|
Orange
|
|
|
|
Watermelon
|
|
|
|
```
|
|
Copytsx
|
|
<SegmentedControl orientation="vertical" defaultValue="Apple">
|
|
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
|
|
<div role="presentation">
|
|
<SegmentedControl.Indicator />
|
|
<div role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
<SegmentedControl.Description>Choose the fruit you like the most.</SegmentedControl.Description>
|
|
</SegmentedControl>
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
<SegmentedControl orientation="vertical" defaultValue="Apple">
|
|
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
|
|
<div role="presentation">
|
|
<SegmentedControl.Indicator />
|
|
<div role="presentation">
|
|
<For each={["Apple", "Orange", "Watermelon"]}>
|
|
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
<SegmentedControl.Description>Choose the fruit you like the most.</SegmentedControl.Description>
|
|
</SegmentedControl>
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### SegmentedControl
|
|
|
|
`SegmentedControl` is equivalent to the `Root` import from `@kobalte/core/segmented-control`.
|
|
|
|
| Prop | Description |
|
|
| --- | --- |
|
|
| value | `string`<br> The controlled value of the item's radio button to check. |
|
|
| defaultValue | `string`<br> The value of the item's radio button that should be checked when initially rendered. Useful when you do not need to control the state of the radio buttons. |
|
|
| onChange | `(value: string) => void`<br> Event handler called when the value changes. |
|
|
| orientation | `'horizontal' | 'vertical'`<br> The axis the segmented control items should align with. |
|
|
| name | `string`<br> The name of the segmented control. Submitted with its owning form as part of a name/value pair. |
|
|
| validationState | `'valid' | 'invalid'`<br> Whether the segmented control should display its "valid" or "invalid" visual styling. |
|
|
| required | `boolean`<br> Whether the user must check a segmented control item before the owning form can be submitted. |
|
|
| disabled | `boolean`<br> Whether the segmented control is disabled. |
|
|
| readOnly | `boolean`<br> Whether the segmented control items can be selected but not changed by the user. |
|
|
|
|
| Data attribute | Description |
|
|
| --- | --- |
|
|
| data-valid | Present when the segmented control is valid according to the validation rules. |
|
|
| data-invalid | Present when the segmented control is invalid according to the validation rules. |
|
|
| data-required | Present when the user must check a segmented control item before the owning form can be submitted. |
|
|
| data-disabled | Present when the segmented control is disabled. |
|
|
| data-readonly | Present when the segmented control is read only. |
|
|
|
|
`SegmentedControl.Label`, `SegmentedControl.Description` and `SegmentedControl.ErrorMesssage` shares the same data-attributes.
|
|
|
|
### SegmentedControl.ErrorMessage
|
|
|
|
| Prop | Description |
|
|
| --- | --- |
|
|
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
|
|
|
|
### SegmentedControl.Item
|
|
|
|
| Prop | Description |
|
|
| --- | --- |
|
|
| value | `string`<br> The value of the item's radio button, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio#Value). |
|
|
| disabled | `boolean`<br> Whether the item's radio button is disabled or not. |
|
|
|
|
| Data attribute | Description |
|
|
| --- | --- |
|
|
| data-valid | Present when the parent segmented control is valid according to the validation rules. |
|
|
| data-invalid | Present when the parent segmented control is invalid according to the validation rules. |
|
|
| data-checked | Present when the segmented control checked. |
|
|
| data-disabled | Present when the segmented control disabled. |
|
|
|
|
`SegmentedControl.ItemInput`, `SegmentedControl.ItemControl`, `SegmentedControl.ItemIndicator` and `SegmentedControl.ItemLabel` shares the same data-attributes.
|
|
|
|
### SegmentedControl.ItemIndicator
|
|
|
|
| Prop | Description |
|
|
| --- | --- |
|
|
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
|
|
|
|
## Rendered elements
|
|
|
|
| Component | Default rendered element |
|
|
| --- | --- |
|
|
| `SegmentedControl` | `div` |
|
|
| `SegmentedControl.Label` | `span` |
|
|
| `SegmentedControl.Description` | `div` |
|
|
| `SegmentedControl.ErrorMessage` | `div` |
|
|
| `SegmentedControl.Indicator` | `div` |
|
|
| `SegmentedControl.Item` | `div` |
|
|
| `SegmentedControl.ItemInput` | `input` |
|
|
| `SegmentedControl.ItemControl` | `div` |
|
|
| `SegmentedControl.ItemIndicator` | `div` |
|
|
| `SegmentedControl.ItemLabel` | `label` |
|
|
| `SegmentedControl.ItemDescription` | `div` |
|
|
|
|
## Accessibility
|
|
|
|
### Keyboard Interactions
|
|
|
|
| Key | Description |
|
|
| --- | --- |
|
|
| `Tab` | Moves focus to either the checked item's radio button or the first item's radio button in the group. |
|
|
| `Space` | When focus is on an unchecked item's radio button, checks it. |
|
|
| `ArrowDown` | Moves focus and checks the next item's radio button in the group. |
|
|
| `ArrowRight` | Moves focus and checks the next item's radio button in the group. |
|
|
| `ArrowUp` | Moves focus and checks the previous item's radio button in the group. |
|
|
| `ArrowLeft` | Moves focus and checks the previous item's radio button in the group. |
|
|
|
|
Previous[←Search](https://kobalte.dev/docs/core/components/search)Next[Select→](https://kobalte.dev/docs/core/components/select) |