WorkspaceTabs
<caos-workspace-tabs>
the app-scoped tab row, with the open records beneath it
May be placed on a seam in the workspace frame.
Inputs
Section titled “Inputs”| Name | Attribute | Description | Type | Default | Required |
|---|---|---|---|---|---|
tabs |
property only | The active app’s destinations, in order, as { id, label, href, active?, object?, caret?, icon?, overflow? }. object marks it as an object tab — its label is set uppercase and the key rides the caret event. caret draws the per-tab chevron. icon names a glyph from the shared vocabulary; a name outside it draws no glyph rather than a blank box. overflow moves the tab out of the bar and into the trailing “More” menu — with none marked, the bar is exactly the plain bar. |
json | none declared | Optional |
homeTabs |
property only | Destinations PINNED into the row, as { id, label, href, icon?, active? }, drawn between the anchor and the app’s tabs. Unlike tabs they survive an app switch, which is the whole of what they promise — so there is no overflow here (a tab that hides itself when the bar is tight is not permanently visible) and no object/caret (these are destinations, not windows onto one object). Choosing one raises the same workspace-navigate an app tab raises. |
json | none declared | Optional |
activeAppLabel |
active-app-label, markup only |
The name of the app that is open, drawn after the launcher in the display face so it reads as the title of the row. Unset ⇒ nothing is drawn there. | text | none declared | Optional |
homeLabel |
home-label, markup only |
What the leading anchor is called. Unset ⇒ “Home”. A shell whose home IS an app passes that app’s name; the anchor never names a package on its own, because whether that package is installed is the shell’s knowledge and not this component’s. | text | Home | Optional |
homeActive |
home-active, markup only |
The home surface is the one on screen, so the leading anchor reads active instead of one of the tabs. | boolean | false | Optional |
Events
Section titled “Events”| Name | When it fires | What it carries |
|---|---|---|
workspace-home |
The leading anchor was chosen. Go home; do NOT change which app is active. | nothing |
workspace-navigate |
A tab was chosen — a pinned one in the bar, or one from the “More” menu. Both raise this, so an overflow tab activates exactly like any other. | { href, id } — where to go, and which tab said so. |
tab-caret |
An object tab’s chevron was pressed. The row opens nothing; it says which object and where the chevron is. | { id, object, anchor: { left, bottom } } — the caret’s live screen rectangle, ready to hand to ObjectCaret’s openAt. |
| Name | What goes in it |
|---|---|
launcher |
The App Launcher, at the head of the row. It stays pinned while the tab set scrolls past it. |
gadget |
The Customize Navigation control, at the far end. Also pinned. |
Styling hooks
Section titled “Styling hooks”| Design token | What it controls |
|---|---|
--caos-color-surface |
The band itself — the DATA surface, distinct from the page base the bar above it paints. |
--caos-color-border |
The rule under the row, the divider after the app name, and the “More” menu’s outline. |
--caos-color-accent |
The underline under the active tab, and the dot beside the active item in the “More” menu. |
--caos-color-bg |
The fill behind the active tab, and behind a “More” menu row on hover. |
--caos-color-text |
The app name, the active tab, and a tab under the pointer. |
--caos-color-text-muted |
A resting tab label and a resting caret. |
--caos-focus-ring |
The ring on a tab, a caret, the “More” button or a menu row reached by keyboard. |
--caos-font-display |
The typeface the current app’s name is set in. |
--caos-font-mono |
The typeface the tab labels and the “More” menu are set in. |
--caos-tracking-tight |
The letter-spacing of the current app’s name. |
--caos-radius-sm |
The corner radius of a focus ring, and of a “More” menu row. |
--caos-radius |
The “More” menu’s corner radius. |
--caos-shadow-float |
The shadow under the “More” menu. |
--caos-space-1 |
The padding inside the “More” menu. |
--caos-space-2 |
The space either side of the divider, and the inset at the far end before the gadget. |
--caos-space-3 |
The gap between the launcher and the current app’s name. |
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; the home anchor and the overflow control carry it too | Always |
When to use it
Section titled “When to use it”Only as the workspace frame’s app-scoped row. The tabs are already scoped by the active app, so switching apps swaps the whole set; nothing here is a general-purpose tab control.
What to use instead
Section titled “What to use instead”- TabStrip — The choices are facets of ONE record or one configuration object, inside a page.
- OpenRecords — The row is the records somebody has open rather than the app’s destinations.
- AppLauncher — Somebody is moving to a different app, not to a destination inside this one.
- SegmentedFilter — The row changes what a list is showing rather than moving anywhere.
Accessibility
Section titled “Accessibility”The anchor and the tabs are a real role="tablist" with a roving tabindex — only the active tab is in the tab order, and Arrow Left/Right, Home and End move between them; the trailing “More” button joins the same roving set so the collapsed tabs stay reachable. The launcher sits OUTSIDE the tablist, because it is a menu button and not a tab. The “More” menu is a role="menu" that takes focus when it opens, roams with Up/Down, jumps with Home/End, closes on Escape returning focus to its button, and marks the active item aria-current; each per-tab caret is a button named “aria-label, and a caret’s aria-expanded belongs to whoever opens the popover it reports.
Examples
Section titled “Examples”An app’s object tabs
Section titled “An app’s object tabs”The row as an app hands it over: the home anchor, then the app’s objects with their glyphs and their per-tab carets, one of them active.
The page around it: The surrounding page is the shell: it answers a caret press by filling in that object’s recents and opening the popover at the rectangle the row reported.
{ "id": "example", "section": "An app’s object tabs", "columns": 1, "items": [ { "id": "workspace_tabs_1", "type": "component", "key": "workspace_tabs", "inputs": { "home_label": "Atlantis AI", "active_app_label": "Sales Atlas", "tabs": [ { "id": "o:account", "label": "Accounts", "href": "/sales-atlas/account", "object": "account", "icon": "building-2", "caret": true, "active": true }, { "id": "o:contact", "label": "Contacts", "href": "/sales-atlas/contact", "object": "contact", "icon": "users", "caret": true }, { "id": "o:invoice", "label": "Invoices", "href": "/sales-atlas/invoice", "object": "invoice", "icon": "file-text", "caret": true }, { "id": "reports", "label": "Reports", "href": "/sales-atlas/reports", "icon": "table-2" } ] }, "children": [ { "id": "object_caret_2", "type": "component", "key": "object_caret", "inputs": { "views": [ { "id": "v1", "label": "All Accounts", "href": "/sales-atlas/account?view=all" }, { "id": "v2", "label": "My Accounts", "href": "/sales-atlas/account?view=mine" } ] } } ] } ]}<caos-workspace-tabs id="workspaceTabs1" home-label="Atlantis AI" active-app-label="Sales Atlas"> <caos-object-caret id="workspaceTabs1objectCaret1"></caos-object-caret></caos-workspace-tabs>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const workspaceTabs1 = document.getElementById('workspaceTabs1');const workspaceTabs1objectCaret1 = document.getElementById('workspaceTabs1objectCaret1');workspaceTabs1.tabs = [ { "id": "o:account", "label": "Accounts", "href": "/sales-atlas/account", "object": "account", "icon": "building-2", "caret": true, "active": true }, { "id": "o:contact", "label": "Contacts", "href": "/sales-atlas/contact", "object": "contact", "icon": "users", "caret": true }, { "id": "o:invoice", "label": "Invoices", "href": "/sales-atlas/invoice", "object": "invoice", "icon": "file-text", "caret": true }, { "id": "reports", "label": "Reports", "href": "/sales-atlas/reports", "icon": "table-2" }];workspaceTabs1objectCaret1.views = [ { "id": "v1", "label": "All Accounts", "href": "/sales-atlas/account?view=all" }, { "id": "v2", "label": "My Accounts", "href": "/sales-atlas/account?view=mine" }];The row as the shell builds it
Section titled “The row as the shell builds it”Both seams filled: the App Launcher pinned at the head and the Customize Navigation control pinned at the far end, with the tab set scrolling between them.
{ "id": "example", "section": "The row as the shell builds it", "columns": 1, "items": [ { "id": "workspace_tabs_1", "type": "component", "key": "workspace_tabs", "inputs": { "home_label": "Atlantis AI", "active_app_label": "Sales Atlas", "home_active": true, "tabs": [ { "id": "o:account", "label": "Accounts", "href": "/sales-atlas/account", "object": "account", "icon": "building-2" }, { "id": "o:invoice", "label": "Invoices", "href": "/sales-atlas/invoice", "object": "invoice", "icon": "file-text" } ] }, "children": [ { "id": "app_launcher_2", "type": "component", "key": "app_launcher", "inputs": { "active_app_key": "sales-atlas", "apps": [ { "key": "sales-atlas", "label": "Sales Atlas", "icon": "building-2", "destinationCount": 2 }, { "key": "setup", "label": "Setup", "icon": "settings", "destinationCount": 12 } ] }, "slot": "launcher" }, { "id": "nav_gadget_3", "type": "component", "key": "nav_gadget", "inputs": { "app_label": "Sales Atlas", "items": [ { "id": "account", "label": "Accounts", "href": "/sales-atlas/account" }, { "id": "invoice", "label": "Invoices", "href": "/sales-atlas/invoice" } ], "available": [ { "id": "order", "label": "Orders", "href": "/sales-atlas/order" } ] }, "slot": "gadget" } ] } ]}<caos-workspace-tabs id="workspaceTabs1" home-label="Atlantis AI" active-app-label="Sales Atlas" home-active> <caos-app-launcher id="workspaceTabs1appLauncher1" slot="launcher" active-app-key="sales-atlas"></caos-app-launcher> <caos-nav-gadget id="workspaceTabs1navGadget2" slot="gadget" app-label="Sales Atlas"></caos-nav-gadget></caos-workspace-tabs>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const workspaceTabs1 = document.getElementById('workspaceTabs1');const workspaceTabs1appLauncher1 = document.getElementById('workspaceTabs1appLauncher1');const workspaceTabs1navGadget2 = document.getElementById('workspaceTabs1navGadget2');workspaceTabs1.tabs = [ { "id": "o:account", "label": "Accounts", "href": "/sales-atlas/account", "object": "account", "icon": "building-2" }, { "id": "o:invoice", "label": "Invoices", "href": "/sales-atlas/invoice", "object": "invoice", "icon": "file-text" }];workspaceTabs1appLauncher1.apps = [ { "key": "sales-atlas", "label": "Sales Atlas", "icon": "building-2", "destinationCount": 2 }, { "key": "setup", "label": "Setup", "icon": "settings", "destinationCount": 12 }];workspaceTabs1navGadget2.items = [ { "id": "account", "label": "Accounts", "href": "/sales-atlas/account" }, { "id": "invoice", "label": "Invoices", "href": "/sales-atlas/invoice" }];workspaceTabs1navGadget2.available = [ { "id": "order", "label": "Orders", "href": "/sales-atlas/order" }];A long tab set
Section titled “A long tab set”An app with more destinations than the bar should carry: the ones marked for overflow collapse into a trailing “More” menu, and the active surface living inside it still underlines the button.
{ "id": "example", "section": "A long tab set", "columns": 1, "items": [ { "id": "workspace_tabs_1", "type": "component", "key": "workspace_tabs", "inputs": { "home_label": "Atlantis AI", "active_app_label": "Sales Atlas", "tabs": [ { "id": "o:account", "label": "Accounts", "href": "/sales-atlas/account", "object": "account", "icon": "building-2" }, { "id": "o:contact", "label": "Contacts", "href": "/sales-atlas/contact", "object": "contact", "icon": "users" }, { "id": "o:invoice", "label": "Invoices", "href": "/sales-atlas/invoice", "object": "invoice", "icon": "file-text" }, { "id": "o:order", "label": "Orders", "href": "/sales-atlas/order", "object": "order", "icon": "file-text", "overflow": true, "active": true }, { "id": "reports", "label": "Reports", "href": "/sales-atlas/reports", "icon": "table-2", "overflow": true } ] } } ]}<caos-workspace-tabs id="workspaceTabs1" home-label="Atlantis AI" active-app-label="Sales Atlas"></caos-workspace-tabs>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const workspaceTabs1 = document.getElementById('workspaceTabs1');workspaceTabs1.tabs = [ { "id": "o:account", "label": "Accounts", "href": "/sales-atlas/account", "object": "account", "icon": "building-2" }, { "id": "o:contact", "label": "Contacts", "href": "/sales-atlas/contact", "object": "contact", "icon": "users" }, { "id": "o:invoice", "label": "Invoices", "href": "/sales-atlas/invoice", "object": "invoice", "icon": "file-text" }, { "id": "o:order", "label": "Orders", "href": "/sales-atlas/order", "object": "order", "icon": "file-text", "overflow": true, "active": true }, { "id": "reports", "label": "Reports", "href": "/sales-atlas/reports", "icon": "table-2", "overflow": true }];