Segmented button
A segmented button is one container split into two to five segments, each an option: a view switcher, a sort order, a set of filters. A selected segment shows a checkmark, so the row keeps its shape while the choice changes. See the M3 segmented buttons guidelines.
M3 Expressive deprecates the segmented button for the connected
button group, and so does mtrl. It still ships and behaves as
described here. To migrate, use kind: 'connected', rename segments to buttons and mode
to selection, and add required: true to a single group with its first button selected;
getValue() becomes getSelected(), and change reports values. There is no web
component.
Usage
In single mode, the first enabled segment is selected when none is.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ text: 'List', value: 'list', selected: true },
{ text: 'Grid', value: 'grid' },
{ text: 'Map', value: 'map' },
],
});
document.body.append(segmentedButton.element);
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ text: 'List', value: 'list', selected: true },
{ text: 'Grid', value: 'grid' },
{ text: 'Map', value: 'map' },
],
});
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ text: 'List', value: 'list', selected: true },
{ text: 'Grid', value: 'grid' },
{ text: 'Map', value: 'map' },
],
});
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ text: 'List', value: 'list', selected: true },
{ text: 'Grid', value: 'grid' },
{ text: 'Map', value: 'map' },
],
});
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ text: 'List', value: 'list', selected: true },
{ text: 'Grid', value: 'grid' },
{ text: 'Map', value: 'map' },
],
});
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ text: 'List', value: 'list', selected: true },
{ text: 'Grid', value: 'grid' },
{ text: 'Map', value: 'map' },
],
});
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
Examples
Multiple selection
mode: 'multi' allows several segments, or none. change carries the selected values as an
array, and fires only when the selection differs, from a click or from select() and
deselect().
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'multi',
segments: [
{ text: '$', value: 'low' },
{ text: '$$', value: 'medium' },
{ text: '$$$', value: 'high' },
],
});
segmentedButton.on('change', ({ value }) => filterByPrice(value));
document.body.append(segmentedButton.element);
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'multi',
segments: [
{ text: '$', value: 'low' },
{ text: '$$', value: 'medium' },
{ text: '$$$', value: 'high' },
],
});
segmentedButton.on('change', ({ value }) => filterByPrice(value));
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'multi',
segments: [
{ text: '$', value: 'low' },
{ text: '$$', value: 'medium' },
{ text: '$$$', value: 'high' },
],
});
segmentedButton.on('change', ({ value }) => filterByPrice(value));
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'multi',
segments: [
{ text: '$', value: 'low' },
{ text: '$$', value: 'medium' },
{ text: '$$$', value: 'high' },
],
});
segmentedButton.on('change', ({ value }) => filterByPrice(value));
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'multi',
segments: [
{ text: '$', value: 'low' },
{ text: '$$', value: 'medium' },
{ text: '$$$', value: 'high' },
],
});
segmentedButton.on('change', ({ value }) => filterByPrice(value));
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'multi',
segments: [
{ text: '$', value: 'low' },
{ text: '$$', value: 'medium' },
{ text: '$$$', value: 'high' },
],
});
segmentedButton.on('change', ({ value }) => filterByPrice(value));
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
Icons and the checkmark
With text only, the checkmark appears before the label; with an icon and text, it replaces the
icon; an icon-only segment keeps its icon. A segment's checkmarkIcon replaces the check.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ icon: walkIcon, text: 'Walk', value: 'walk', selected: true },
{ icon: bikeIcon, text: 'Bike', value: 'bike' },
{ icon: carIcon, text: 'Drive', value: 'drive' },
],
});
document.body.append(segmentedButton.element);
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ icon: walkIcon, text: 'Walk', value: 'walk', selected: true },
{ icon: bikeIcon, text: 'Bike', value: 'bike' },
{ icon: carIcon, text: 'Drive', value: 'drive' },
],
});
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ icon: walkIcon, text: 'Walk', value: 'walk', selected: true },
{ icon: bikeIcon, text: 'Bike', value: 'bike' },
{ icon: carIcon, text: 'Drive', value: 'drive' },
],
});
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ icon: walkIcon, text: 'Walk', value: 'walk', selected: true },
{ icon: bikeIcon, text: 'Bike', value: 'bike' },
{ icon: carIcon, text: 'Drive', value: 'drive' },
],
});
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ icon: walkIcon, text: 'Walk', value: 'walk', selected: true },
{ icon: bikeIcon, text: 'Bike', value: 'bike' },
{ icon: carIcon, text: 'Drive', value: 'drive' },
],
});
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
import { createSegmentedButton } from 'mtrl';
const segmentedButton = createSegmentedButton({
mode: 'single',
segments: [
{ icon: walkIcon, text: 'Walk', value: 'walk', selected: true },
{ icon: bikeIcon, text: 'Bike', value: 'bike' },
{ icon: carIcon, text: 'Drive', value: 'drive' },
],
});
document.body.append(segmentedButton.element);
Web Components, React, Vue, Svelte and SolidJS come with the segmented button web component; the vanilla factory works in any of them today.
API
Options
| Option | Type | Default | Description |
|---|---|---|---|
segments |
SegmentConfig[] |
undefined |
The segments, in order |
mode |
SelectionMode | 'single' | 'multi' |
'single' |
One selection at a time, or several |
density |
Density | string |
'default' |
'default', 'comfortable' or 'compact' |
disabled |
boolean |
false |
Disables every segment |
ripple |
boolean |
true |
Whether a press shows the ripple |
rippleConfig |
{ duration?, timing?, opacity? } |
undefined |
Only duration applies: how long, in ms, a released wave lingers before it is removed. timing and opacity are accepted and not applied |
class |
string |
undefined |
Extra classes on the container |
prefix |
string |
'mtrl' |
Prefix for CSS class names |
on |
{ change? } |
undefined |
Accepted but not applied: register handlers with on() |
SelectionMode and Density are string enums exported by mtrl/components/segmented-button.
Each segment
| Option | Type | Default | Description |
|---|---|---|---|
text |
string |
undefined |
Label |
icon |
string |
undefined |
Icon as an HTML string |
value |
string |
the text | Identifies the segment in the methods and events |
selected |
boolean |
false |
Initially selected |
disabled |
boolean |
false |
Disables this segment only |
checkmarkIcon |
string |
a filled check | Replaces the selected-state checkmark |
class |
string |
undefined |
Extra classes on this segment |
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
getSelected() |
none | Segment[] |
The selected segments |
getValue() |
none | string[] |
Their values |
select(value) |
value: string |
SegmentedButtonComponent |
Selects that segment, disabled or not; in single mode it deselects the others. An unknown value clears the selection |
deselect(value) |
value: string |
SegmentedButtonComponent |
Deselects it; in single mode, not the last selected one |
enable() / disable() |
none | SegmentedButtonComponent |
Every segment; enable() leaves the individually disabled ones |
enableSegment(value) / disableSegment(value) |
value: string |
SegmentedButtonComponent |
One segment, by value |
setDensity(density) / getDensity() |
density: Density | string |
SegmentedButtonComponent / string |
The density class, which sets height and padding |
on(event, handler) / off(event, handler) |
event: 'change', handler: Function |
SegmentedButtonComponent |
Adds or removes a listener |
destroy() |
none | void |
Destroys every segment and releases the container |
| Property | Type | Description |
|---|---|---|
element |
HTMLElement |
The role="group" container |
segments |
Segment[] |
The segments, in order: element, value, isSelected(), setSelected(), isDisabled(), setDisabled(), destroy(). Their setters emit no change |
Events
| Event | Description | Data |
|---|---|---|
change |
The selection changed | { selected, value, oldValue } |
selected holds the selected Segments, value their values, oldValue the values before.
Accessibility
- The container is a
role="group"named"Segmented button"; to name it better, setaria-labelonelement. - Each segment is a native
<button>witharia-pressed. Its name is its text, or itsvalue: give an icon-only segment avaluethat reads as a label. Tabreaches every segment;SpaceandEnteractivate it. There is no arrow-key navigation.- Focus shows as a 2dp
secondaryoutline inside the segment, which the rounded ends don't clip.
Styling
The container carries data-mode and data-density. The stylesheet sets the sizes as custom
properties per density class, and hands each segment its corners through the button's
--mtrl-button-shape properties: only the first and last segments round off.
.mtrl-segmented-button { }
.mtrl-segmented-button--comfortable, .mtrl-segmented-button--compact,
.mtrl-segmented-button--disabled { }
.mtrl-segmented-button__segment, .mtrl-segment--selected, .mtrl-segment-checkmark { }
.filters .mtrl-segmented-button {
--segment-height: 40px;
--segment-padding-x: 24px;
--segment-padding-icon-only: 12px;
--segment-padding-icon-text-left: 12px;
--segment-padding-icon-text-right: 16px;
--segment-border-radius: 20px;
}
Measurements
| Attribute | Value | Source |
|---|---|---|
| Height, default / comfortable / compact | 40 / 36 / 32dp | OutlinedSegmentedButtonTokens, less 4dp per density step |
| Container corner | half the height | --segment-border-radius |
| Minimum segment width | 48dp | min-width on the segment |
| Horizontal padding | 24dp | --segment-padding-x |
| Hover and pressed state layers | 8% and 12% on-surface |
the M3 state layers |