Skip to content

SegmentedFilter

<caos-segmented-filter>

a row of mutually exclusive quick filters, one of them in force

May be placed on an app’s home page and a page inside an app.

Name Attribute Description Type Default Required
options property only The segments, as { value, label } objects or as plain strings, in the order they are drawn. A string becomes both the value and the label. json none declared Optional
value value, also a property Which segment is in force. Reflected as an attribute, so it can be read back off the element. When the page sets none, the FIRST option becomes the value as soon as the segments are drawn. text none declared Optional
label label, markup only The accessible name of the control — WHAT is being filtered, in the words a screen reader reads out: Scope, Period, Owner. Follows the attribute whenever it changes. Without one the group is announced as “Filter”, which distinguishes nothing on a toolbar carrying two. text none declared Optional
Name When it fires What it carries
change A segment is chosen and it was not already in force — by pointer, or by an arrow key, which moves and selects in one gesture. Re-choosing the segment already in force raises nothing. { value } — the value of the segment now in force.

Nothing goes inside this piece.

Design token What it controls
--caos-color-accent-fill The fill and border of the segment in force.
--caos-color-accent-contrast The ink of the segment in force. White by default; a brand with a pale accent sets a darker ink here.
--caos-color-surface-2 The inset track the segments sit in.
--caos-color-border The outline of the track.
--caos-color-text-muted The ink of the segments that are not in force.
--caos-color-text The ink of a hovered segment.
--caos-font-mono The typeface of the segment labels.
--caos-focus-ring The ring drawn on a keyboard-focused segment.
--caos-radius-sm The corner radius of each segment.

Nothing else drives this piece by calling it.

Nothing on this piece can be read back.

Part Which piece of it When it is there
::part(group) the group of segments Always
::part(segment) one segment Always

Two to five mutually exclusive filters that a person switches between constantly and should be able to read all of at once. If they would not all fit on one line, this is the wrong control.

  • TabStrip — The row swaps the SUBJECT rather than narrowing a set — Details, Related, History on one record. That is a real ARIA tablist, it can own the panels beneath it, and it is announced as tabs. This one is announced as pressed buttons and owns nothing below it.
  • PicklistField — There are more choices than fit on a line, or the choice is a VALUE ON THE RECORD being saved rather than a view of a list.
  • Button — Pressing it DOES something rather than changing what is shown.
  • WorkspaceTabs — You are moving between objects in an app rather than filtering one list. That belongs to the workspace frame and is not placed on a page at all.

The segments are real buttons inside a role="group", each carrying aria-pressed, so the state is announced as pressed rather than as a selected tab — which is the truthful reading of a filter. Keyboard is a roving tabindex: one Tab stop for the whole control, and Arrow Left/Right/Up/Down, Home and End move focus AND change the filter in one gesture. Focus rings, tab order and the default group name (“Filter”) are handled. Left to the author: set label to say what is being filtered — a control announced as “Filter” on a page with two of them says nothing. label is an input rather than an aria-label attribute so that a page assembled from stored metadata can set it; aria-label written straight onto the element is still read, and label wins where both are given. Because arrow keys select as they move, avoid this control where each change is expensive.

Three named subsets with one in force — how a list toolbar narrows what a table shows.

{
"id": "example",
"section": "Scoping a list",
"columns": 1,
"items": [
{
"id": "segmented_filter_1",
"type": "component",
"key": "segmented_filter",
"inputs": {
"label": "Scope",
"value": "active",
"options": [
{
"value": "active",
"label": "Active"
},
{
"value": "mine",
"label": "Mine"
},
{
"value": "all",
"label": "All"
}
]
}
}
]
}
<caos-segmented-filter id="segmentedFilter1" label="Scope" value="active"></caos-segmented-filter>
// What an attribute cannot hold, set as a property — the same values the preview is drawn with.
const segmentedFilter1 = document.getElementById('segmentedFilter1');
segmentedFilter1.options = [
{
"value": "active",
"label": "Active"
},
{
"value": "mine",
"label": "Mine"
},
{
"value": "all",
"label": "All"
}
];

Segments given as bare strings and no value supplied — the labels become their own values and the first segment takes force on its own, so a page that sets nothing is still in a valid state.

{
"id": "example",
"section": "Plain strings, no value set",
"columns": 1,
"items": [
{
"id": "segmented_filter_1",
"type": "component",
"key": "segmented_filter",
"inputs": {
"label": "Period",
"options": [
"Week",
"Month",
"Quarter",
"Year"
]
}
}
]
}
<caos-segmented-filter id="segmentedFilter1" label="Period"></caos-segmented-filter>
// What an attribute cannot hold, set as a property — the same values the preview is drawn with.
const segmentedFilter1 = document.getElementById('segmentedFilter1');
segmentedFilter1.options = [
"Week",
"Month",
"Quarter",
"Year"
];