WorkTabs
<caos-work-tabs>
several pieces of work open at once in one destination, each in its own tab
May be placed on a page inside an app.
Inputs
Section titled “Inputs”| Name | Attribute | Description | Type | Default | Required |
|---|---|---|---|---|---|
tabs |
property only | The open work, as { id, openedAs, namedBy?, dirty? } objects in the order they are drawn. openedAs is the name the tab was created with (“New Query 1”); namedBy is what its content calls it (“Account @10:38”) and wins whenever it is set to anything but blank, so a tab renames itself as soon as it holds something and falls back rather than going nameless if that content is cleared. dirty draws the unsaved mark and makes closing ask first. |
json | none declared | Optional |
value |
value, also a property |
The id of the active tab, reflected as an attribute so it can be read back off the element. A value naming no open tab falls to the first one rather than leaving the strip with nothing lit, which would read as “nothing is open”. | text | none declared | Optional |
max |
max, markup only |
How many pieces of work this destination may hold at once. At the cap the “new” control is disabled and explains itself, and the new event is not raised even by a programmatic click — the cap is a rule about how many tabs may exist, not a look the control wears. A value that is not a positive whole number is read as unset and falls back to the default, rather than being honoured as a cap of zero and making the destination unusable through a typo. | number | 12 | Optional |
newLabel |
new-label, markup only |
The word on the control that opens another piece of work. Worth setting to the noun the destination deals in — “New query”, “New request” — so the control says what it will make. | text | New | Optional |
Events
Section titled “Events”| Name | When it fires | What it carries |
|---|---|---|
work-tab-select |
A tab becomes active and was not already — by pointer, or by an arrow key, which moves and activates in one gesture. | { id } — the id of the tab now active. |
work-tab-new |
The “new” control was used. Never raised at the cap, so a listener can open a tab without counting first. | nothing |
work-tab-close |
A tab is to be closed. For a CLEAN tab this is immediate. For one with unsaved work it is raised only after the person has answered the question, so a listener can close on it directly and never has to ask again — by the time it arrives, somebody has said yes. | { id } — the id of the tab to close. |
Nothing goes inside this piece.
Styling hooks
Section titled “Styling hooks”| Design token | What it controls |
|---|---|
--caos-color-accent |
The underline under the active tab, and the unsaved mark. |
--caos-color-border |
The rule the strip sits on, and the outline of the buttons in the question. |
--caos-color-surface |
The band the strip is drawn on. |
--caos-color-bg |
The ground the question about unsaved work sits on, and a hovered close mark. |
--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, and of the new control. |
--caos-font-mono |
The typeface of the tab names and the new control. |
--caos-focus-ring |
The ring drawn on a keyboard-focused tab or button. |
--caos-radius-sm |
The corners of the focus ring, the close mark and the question buttons. |
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 piece of open work | Always |
::part(close) |
the mark that closes a tab | Always |
::part(new) |
the control that opens another one | Always |
::part(ask) |
the question asked before closing unsaved work | Always |
When to use it
Section titled “When to use it”A screen where somebody genuinely works on several things at once and moving between them must not lose any of them — a query editor, an API console, a log viewer. Reach for it when the alternative is one pane that has to be emptied before the next thing can be started, which is the complaint the incumbent developer console draws.
What to use instead
Section titled “What to use instead”- TabStrip — The tabs are FACETS of one thing rather than separate pieces of work — Details, Related, History. That set is authored and fixed, nothing is opened or closed, and nothing in it can hold unsaved work of its own.
- WorkspaceTabs — You are moving between destinations of an application rather than between things open inside one destination. That is part of the workspace frame and is not placed on a page.
- SplitView — Everything should be visible at once — a list beside the record it opens — rather than one thing at a time with the rest waiting in a strip.
Accessibility
Section titled “Accessibility”A WAI-ARIA tablist: role="tablist" around role="tab" buttons with aria-selected on the active one and a roving tabindex, so the whole strip is one Tab stop and Arrow Left/Right/Up/Down, Home and End move and activate. The close mark inside a tab is a span and not a button on purpose — a button is not a valid child of role="tab" — so the keyboard route to closing is Delete or Backspace on the focused tab, which is what the authoring practice for closable tabs prescribes. The unsaved mark carries the name “unsaved” rather than being colour alone. The question about closing unsaved work is an alertdialog, labelled by the sentence inside it so a screen reader says which tab has unsaved work, and it takes focus when it appears — on KEEPING it, because the safe answer is the one a second keystroke should reach. It is deliberately not aria-modal: it is a row under the strip and traps nothing, and the rest of the page is still there. Declining returns focus to the tab that was nearly lost, and closing one returns it to the tab the destination moved to rather than to nowhere. Left to the author: set aria-label saying what this destination holds — the default name is “Open work”, which distinguishes nothing on a screen with two strips.
Examples
Section titled “Examples”Several queries open at once
Section titled “Several queries open at once”Three tabs: two that have run and named themselves from what they returned, and one still wearing the name it was opened with because it holds nothing yet.
Cannot be shown as a page placement: aria-label, which is set on the element rather than declared as an input. A stored placement carries only a component’s own inputs, so a page copying this would place the piece without it.
<caos-work-tabs id="workTabs1" aria-label="Open queries" new-label="New query" value="q2"></caos-work-tabs>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const workTabs1 = document.getElementById('workTabs1');workTabs1.tabs = [ { "id": "q1", "openedAs": "New Query 1", "namedBy": "Account @10:38" }, { "id": "q2", "openedAs": "New Query 2", "namedBy": "Opportunity @10:41" }, { "id": "q3", "openedAs": "New Query 3" }];One of them has unsaved work
Section titled “One of them has unsaved work”The second tab carries the unsaved mark. Closing that one puts the question on screen and raises nothing until it is answered; closing either of the others is immediate.
Cannot be shown as a page placement: aria-label, which is set on the element rather than declared as an input. A stored placement carries only a component’s own inputs, so a page copying this would place the piece without it.
<caos-work-tabs id="workTabs1" aria-label="Open requests" new-label="New request" value="r2" max="6"></caos-work-tabs>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const workTabs1 = document.getElementById('workTabs1');workTabs1.tabs = [ { "id": "r1", "openedAs": "New request 1", "namedBy": "GET /accounts" }, { "id": "r2", "openedAs": "New request 2", "namedBy": "POST /orders", "dirty": true }, { "id": "r3", "openedAs": "New request 3" }];