TabStrip
<caos-tab-strip>
horizontal facets of one record or configuration object
May be placed on a record page and a page inside an app.
Inputs
Section titled “Inputs”| Name | Attribute | Description | Type | Default | Required |
|---|---|---|---|---|---|
items |
property only | The tabs, as { value, label, disabled? } objects or as plain strings, in the order they are drawn. A disabled tab is drawn faded, cannot be chosen, and is skipped by the arrow keys. | json | none declared | Optional |
value |
value, also a property |
Which tab is active, and therefore which panel shows. Reflected as an attribute, so it can be read back off the element. When the page sets none, the first ENABLED tab becomes the value as soon as the tabs are drawn. | text | none declared | Optional |
label |
label, markup only |
The accessible name of the tab row — what the tabs are facets OF, in the words a screen reader reads out: Record facets, Order facets. Follows the attribute whenever it changes. Without one the row is announced as “Tabs”, which distinguishes nothing on a page carrying two. | text | none declared | Optional |
Events
Section titled “Events”| Name | When it fires | What it carries |
|---|---|---|
change |
A tab becomes active and was not already — by pointer, or by an arrow key, which moves focus and activates in one gesture. Re-choosing the active tab raises nothing. | { value } — the value of the tab now active. |
| Name | What goes in it |
|---|---|
panel-<tab value> |
The panel for the tab whose value this names — panel-details for the tab valued details. Wired up as a tabpanel: labelled by its tab, focusable, shown when its tab is active and hidden when it is not. Optional, and per tab: a tab with no slot filled simply has no panel, and a page may fill some and leave others to be drawn elsewhere. More than one element in one tab’s slot is allowed, and all of them are that tab’s panel. |
panel |
The older way of saying the same thing, still supported and still what hand-written markup usually says: one element per tab in a single shared slot, each carrying data-value matching its tab. A data-value is set on the element rather than declared, so a page assembled from stored metadata cannot write one — which is why the per-tab slots above exist. Elements whose data-value matches no tab are simply left alone. |
Styling hooks
Section titled “Styling hooks”| Design token | What it controls |
|---|---|
--caos-color-accent |
The underline under the active tab. |
--caos-color-border |
The rule the tab row sits on. |
--caos-color-text |
The ink of the active tab and of a hovered one. |
--caos-color-text-muted |
The ink of the tabs that are not active. |
--caos-font-mono |
The typeface of the tab labels. |
--caos-focus-ring |
The ring drawn on a keyboard-focused tab. |
--caos-space-4 |
The gap between tabs, and the space above the panel body. |
Methods
Section titled “Methods”Nothing else drives this piece by calling it.
Readable state
Section titled “Readable state”Nothing on this piece can be read back.
Styling parts
Section titled “Styling parts”| Part | Which piece of it | When it is there |
|---|---|---|
::part(tablist) |
the row of tabs | Always |
::part(tab) |
one tab | Always |
::part(panels) |
the area the panels are slotted into | Always |
When to use it
Section titled “When to use it”One subject with several faces, only one of which is worth looking at at a time. Reach for it when the alternative is a very long page a person has to scroll to find the part they came for.
What to use instead
Section titled “What to use instead”- SegmentedFilter — The row NARROWS A SET rather than swapping the subject — All, Active, Mine on a list of records. That one is announced as pressed buttons rather than as tabs, draws as a filled pill rather than an underline, and belongs on the toolbar of the list it filters.
- WorkspaceTabs — You are moving between OBJECTS in an app rather than between facets of one record. That is a part of the workspace frame and is not placed on a page at all.
- DetailSection — Everything should be visible at once and only wants dividing up. Sections stack, so a person can read the whole record and print it; tabs hide what they are not showing.
- SplitView — A person is moving between several RECORDS while keeping the list in sight, not between facets of one.
Accessibility
Section titled “Accessibility”A real WAI-ARIA tablist: role="tablist" around role="tab" buttons, aria-selected on the active one, and any slotted panel wired up as role="tabpanel" with aria-labelledby back to its tab and aria-controls forward to the panel — the author puts the panel in that tab’s slot and gets all of that. Keyboard is a roving tabindex: one Tab stop for the whole row, then Arrow Left/Right/Up/Down, Home and End move and activate, skipping disabled tabs. Panels are made focusable so Tab from the row lands in the content. Left to the author: set label saying what the tabs are facets OF — the default name is “Tabs”, which distinguishes nothing on a page with two rows. 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 activate as they move, avoid a tab whose content is expensive to produce.
Examples
Section titled “Examples”Facets of a record
Section titled “Facets of a record”The tab row on its own, with the surface drawing the body itself and listening for the change.
{ "id": "example", "section": "Facets of a record", "columns": 1, "items": [ { "id": "tab_strip_1", "type": "component", "key": "tab_strip", "inputs": { "label": "Record facets", "value": "details", "items": [ { "value": "details", "label": "Details" }, { "value": "related", "label": "Related" }, { "value": "history", "label": "History" } ] } } ]}<caos-tab-strip id="tabStrip1" label="Record facets" value="details"></caos-tab-strip>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const tabStrip1 = document.getElementById('tabStrip1');tabStrip1.items = [ { "value": "details", "label": "Details" }, { "value": "related", "label": "Related" }, { "value": "history", "label": "History" }];Owning its panels
Section titled “Owning its panels”The same tabs with slotted panels that really switch — each body sits in its own tab’s slot, and the strip shows one and hides the rest without the surrounding page doing anything.
{ "id": "example", "section": "Owning its panels", "columns": 1, "items": [ { "id": "tab_strip_1", "type": "component", "key": "tab_strip", "inputs": { "label": "Order facets", "value": "details", "items": [ { "value": "details", "label": "Details" }, { "value": "related", "label": "Related" }, { "value": "history", "label": "History" } ] }, "children": [ { "id": "detail_section_2", "type": "component", "key": "detail_section", "inputs": { "heading": "Order information" }, "slot": "panel-details", "children": [ { "id": "key_value_row_3", "type": "component", "key": "key_value_row", "inputs": { "label": "Amount", "value": "$12,400" } }, { "id": "key_value_row_4", "type": "component", "key": "key_value_row", "inputs": { "label": "Opened", "value": "14 Mar 2026" } } ] }, { "id": "detail_section_5", "type": "component", "key": "detail_section", "inputs": { "heading": "Related records" }, "slot": "panel-related", "children": [ { "id": "key_value_row_6", "type": "component", "key": "key_value_row", "inputs": { "label": "Account", "value": "Meridian Supply" } }, { "id": "key_value_row_7", "type": "component", "key": "key_value_row", "inputs": { "label": "Owner", "value": "Dana Whitfield" } } ] }, { "id": "empty_state_8", "type": "component", "key": "empty_state", "inputs": { "title": "No changes recorded" }, "slot": "panel-history", "children": [ "Edits to this record will be listed here." ] } ] } ]}<caos-tab-strip id="tabStrip1" label="Order facets" value="details"> <caos-detail-section slot="panel-details" heading="Order information"> <caos-key-value-row label="Amount" value="$12,400"></caos-key-value-row> <caos-key-value-row label="Opened" value="14 Mar 2026"></caos-key-value-row> </caos-detail-section> <caos-detail-section slot="panel-related" heading="Related records"> <caos-key-value-row label="Account" value="Meridian Supply"></caos-key-value-row> <caos-key-value-row label="Owner" value="Dana Whitfield"></caos-key-value-row> </caos-detail-section> <caos-empty-state slot="panel-history" title="No changes recorded">Edits to this record will be listed here.</caos-empty-state></caos-tab-strip>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const tabStrip1 = document.getElementById('tabStrip1');tabStrip1.items = [ { "value": "details", "label": "Details" }, { "value": "related", "label": "Related" }, { "value": "history", "label": "History" }];A facet that is not available
Section titled “A facet that is not available”One tab disabled — drawn faded, refused to the pointer, and stepped over by the arrow keys rather than focused and then rejected.
{ "id": "example", "section": "A facet that is not available", "columns": 1, "items": [ { "id": "tab_strip_1", "type": "component", "key": "tab_strip", "inputs": { "label": "Order facets", "value": "details", "items": [ { "value": "details", "label": "Details" }, { "value": "related", "label": "Related" }, { "value": "approvals", "label": "Approvals", "disabled": true } ] } } ]}<caos-tab-strip id="tabStrip1" label="Order facets" value="details"></caos-tab-strip>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const tabStrip1 = document.getElementById('tabStrip1');tabStrip1.items = [ { "value": "details", "label": "Details" }, { "value": "related", "label": "Related" }, { "value": "approvals", "label": "Approvals", "disabled": true }];