Tabs
Tab-based content switching with arrow-key navigation; inactive panels are hidden via the hidden attribute. Use oas-tabs together with oas-tab-panel.
Basic usage
Basic usage
Content 1: basic information display.
Content 2: more details.
Content 3: other supplementary notes.
Default selection
Specify active
Content 1
Content 2
Content 3 selected by default
Rich content panels
Rich content
Card style
Card-style tabs
Content 1: card-style tabs have borders; the active tab connects with the panel, and the whole is wrapped by a four-side border.
Content 2: more details.
Content 3: other supplementary notes.
Switch to the card style with type="card": every tab has its own border, the active tab's bottom edge shares the panel's background color (connected without a break), and the whole is wrapped by a continuous four-side border.
Switch event
oas-change event
Content 1
Content 2
Closable
closable: each tab shows a close × on the right (span[tabindex="-1"], named for screen readers via aria-label, triggered by Enter / Space). Clicking × fires oas-close with detail: { key }; the component does not remove the panel automatically — the host removes it (the tab bar then refreshes incrementally).
Closable tabs
Content 1
Content 2
Content 3
Closing an inactive tab: the tab disappears immediately (visible feedback). Closing the active tab: it automatically switches to the first remaining tab and shows a message.
Right-click menu (new tab + bulk close)
context-menu: right-click any tab to open an action menu — New / Close / Close others / Close all to the left / Close all to the right / Close all (a separator sits between "New" and the close group). "New" fires oas-add (same contract as the addable + button; detail.label is the locale default name and hosts are free to ignore it and use their own — real-world tabs often mean "new file" and the like, so the menu label stays the neutral "New"); each close operation fires oas-close (detail: { key }) once per target tab, and the host removes the matching panels (same contract as closable). The popup closes on outside click or Escape.
Right-click bulk close (context-menu)
Dashboard content
Orders content
Products content
Users content
Settings content
Right-click “Products” and try “New” and “Close all to the left” / “Close others” — "New" fires
oas-addand the host appends a panel; each close firesoas-closeonce per key, and the host removes the matching panels.
This interaction is part of the manager capability package: the main entry
@oas-ui/ui/navigation/tabsalready ships it (as do the full entry and the CDN navigation bundle) — no explicit import needed. If you only want the lightweight switch/close/overflow baseline, import the pure-core entry@oas-ui/ui/navigation/tabs/coreinstead — it omits the manager capability, and using these options there logs a dev hint pointing toimport '@oas-ui/ui/navigation/tabs/manager'(or back to the main entry).
Per-tab context menu event (host-built menus)
oas-tab-contextmenu: fired when a tab is right-clicked (or a focused tab gets Menu key / Shift+F10) — cancelable; detail: { value, index, clientX, clientY, originalEvent }. When the host calls preventDefault(), both the built-in menu (context-menu attribute) and the browser default menu are suppressed, so the host can look up its own data via value and open oas-context-menu at clientX/clientY (e.g. file-type actions like "Copy path / Open in file manager"). If not canceled, the built-in menu opens as usual. Not fired on empty nav area; keyboard-triggered coordinates use the button center. Independent of the context-menu attribute — the event fires whether or not the built-in menu is enabled. Note: the keyboard path only serves host-built menus (the built-in menu is invoked via mouse right-click).
Hosts can reword the menu per business domain (e.g. "New file"): override individual locale keys —
setLocale({ name: 'en', messages: { ...en.messages, 'tabs.ctxNew': 'New file' } })(importsetLocalefrom@oas-ui/i18n,enfrom@oas-ui/i18n/en; same-name registration overwrites, touch only the keys you need).
Badges
The badge attribute of oas-tab-panel renders a badge (number or text) next to the tab title.
Tabs with badges
Content 1: the badge shows a count.
Content 2: the badge can also display text.
Content 3: no badge.
Dynamic add/remove tabs
addable: shows a + button at the end of the tab bar (aria-label from locale); clicking fires oas-add (detail: { label } — the default new-tab label "New tab" comes from locale, use it directly or customize it). The component does not add a panel — the host appends an oas-tab-panel on oas-add and the tab bar refreshes incrementally; combine with closable to add and remove. After adding, the selection and keyboard focus (roving tabindex) land on the new tab; after closing the active tab, focus moves to the remaining selected tab.
Dynamic add/remove tabs
Content 1: close with ×, add with +.
Content 2: keep adding/removing.
Icon tabs
The icon attribute of oas-tab-panel renders an icon before the tab title (reuses the oas-icon icon set) as an icon + text combo. You can also put a direct child with slot="icon" inside the panel as a custom icon (emoji / SVG etc.).
Icon tabs
Content 1: icon rendered via the `icon` attribute.
Content 2: icon + text combo.
Content 3.
Content 4: custom icon via `slot="icon"`.
Tab position
tab-position: top (default — tabs in a horizontal row above the content) / left (tabs stacked on the left, content on the right) / right / bottom.
left vertical tabs
Content 1: tabs stack vertically on the left, content on the right.
Content 2
Content 3
right vertical tabs
Content 1: tabs stack vertically on the right, content on the left.
Content 2
Content 3
bottom tabs
Content 1: tabs sit horizontally at the bottom, content above.
Content 2
Content 3
Disabled tab
Add disabled to an oas-tab-panel to disable a single tab: not focusable/clickable, aria-disabled, visually dimmed, and skipped by arrow-key navigation.
Disabled tab
Content 1: switches normally.
Content 2: this tab is disabled.
Content 3: arrow keys skip the disabled tab in between.
Size
size: five steps xs / small / medium (default) / large / xl; font-size and padding follow the step.
Size steps
small
Content
medium
Content
large
Content
Centered & justified
centered: center the tab bar; justified: distribute tabs evenly across the full row width.
Centered tabs
Content 1: the tab bar is centered.
Content 2
Content 3
Justified tabs
Content 1: tabs fill the whole row evenly.
Content 2
Content 3
Overflow scrolling
When there are more tabs than fit, the tab bar scrolls horizontally with left/right arrows (vertical scroll + up/down arrows for tab-position="left/right"), and the mouse wheel also slides the tabs horizontally. without-scroll-controls hides the arrows (scrolling remains). Newly added/activated tabs auto-scroll into view (never hidden at the far end on overflow); the addable + button stays pinned at the end of the tab bar, never scrolled out of view.
Overflow scrolling
Content 1
Content 2
Content 3
Content 4
Content 5
Content 6
Content 7
Content 8
Content 9
Content 10
Content 11
Content 12
Scroll arrows off
Content 1
Content 2
Content 3
Content 4
Content 5
Content 6
Content 7
Content 8
Content 9
Content 10
Content 11
Content 12
scroll-position: alignment when scrolling the active/added tab into view — auto (default nearest, minimal scroll) / start / center / end.
Scroll active to center
Content 1
Content 2
Content 3
Content 4
Content 5
Content 6
Content 7
Content 8
Content 9
Content 10
Content 11
Content 12
More collapse dropdown
more: when tabs overflow, the tab bar scrolls horizontally (wheel/drag), and a trailing "More" dropdown lists the tabs outside the current scroll viewport as quick shortcuts (all tabs stay rendered, none hidden — the industry-standard scroll + mirror approach). With many off-view items a search box at the top of the dropdown filters them live; selecting a dropdown item scrolls it smoothly into view (the active tab and its neighbors naturally come into view together thanks to their continuous layout). Newly added/activated tabs auto-scroll smoothly into view.
More collapse dropdown
Content 1
Content 2
Content 3
Content 4
Content 5
Content 6
Content 7
Content 8
Content 9
Content 10
Content 11
Content 12
Panel visibility strategy
panel-mode: keep (default, inactive panels stay in DOM via hidden) / lazy (unvisited panel content is not mounted until first activation, then stays) / destroy (inactive panel content is unmounted on switch). Use lazy/destroy for heavy panels (charts/editors) so inactive panels don't keep consuming resources.
Panel visibility strategy
lazy: unvisited panel content is not mounted yet.
I render only when first activated.
destroy: my content unmounts on switch.
Remounts when switching back.
Manual activation
activation="manual": arrow keys move focus only; Enter / Space switches the panel (a11y manual activation, good when panel content is heavy and switching is costly). Default auto (arrow keys switch immediately).
Manual activation
Content 1: arrows move focus, Enter/Space switches.
Content 2
Content 3
Switch animation
animated: selection transition + panel fade-in (animates color/border/opacity only, no layout).
Switch animation
Content 1: switching animates.
Content 2
Content 3
Before-change interception
The oas-before-change event (cancelable) fires before switching with detail: { value }; the host can preventDefault() to veto the switch (works for both click and keyboard). Useful for "block switching when there are unsaved changes".
Before-change interception
Content 1: check the box above, then switching is vetoed.
Content 2
Content 3
Editable rename
Add editable to an oas-tab-panel: double-click the tab to enter an input editing state; Enter confirms (emits oas-rename with detail: { value, label }, and the component writes the new label back); Esc cancels; blur commits (unchanged content exits silently).
This interaction is part of the manager capability package: the main entry
@oas-ui/ui/navigation/tabsalready ships it (as do the full entry and the CDN navigation bundle) — no explicit import needed. If you only want the lightweight switch/close/overflow baseline, import the pure-core entry@oas-ui/ui/navigation/tabs/coreinstead — it omits the manager capability, and using these options there logs a dev hint pointing toimport '@oas-ui/ui/navigation/tabs/manager'(or back to the main entry).
Editable rename
Content 1: double-click my tab to rename.
Content 2
Content 3: this tab cannot be renamed.
Drag sorting
sortable: tabs can be drag-sorted (native HTML5 drag & drop). After drop it emits oas-reorder with detail: { fromIndex, toIndex }; the component does not move DOM itself — the host reorders the oas-tab-panel list accordingly.
This interaction is part of the manager capability package: the main entry
@oas-ui/ui/navigation/tabsalready ships it (as do the full entry and the CDN navigation bundle) — no explicit import needed. If you only want the lightweight switch/close/overflow baseline, import the pure-core entry@oas-ui/ui/navigation/tabs/coreinstead — it omits the manager capability, and using these options there logs a dev hint pointing toimport '@oas-ui/ui/navigation/tabs/manager'(or back to the main entry).
Drag sorting
Content 1: drag tabs to reorder.
Content 2
Content 3
Content 4
Nested tabs
An oas-tab-panel can nest another oas-tabs; inner and outer manage their own selection independently (the outer only recognizes its direct child panels, not the inner ones).
Nested tabs
Outer content: overview.
Inner content: basic info.
Inner content: advanced settings.
Custom tab label
Place a direct child element with slot="label" inside oas-tab-panel to fully customize the tab label (rich text / icon combos), falling back to the label attribute as plain text. That element is not projected into the panel's default slot — it is reserved for the tab label slot.
Custom tab label
Content 1: default text label.
Content 2: custom rich-text label.
Content 3: label with a status dot.
Hover switching
trigger="hover": switch tabs on hover (default click). Disabled tabs are not triggered on hover.
Hover switching
Content 1: switch on hover.
Content 2
Content 3
Allow deactivation
allow-deactivation: clicking the currently active tab deactivates it (no selection). By default, clicking the active tab keeps it active.
Allow deactivation
Content 1: click the active tab again to deactivate.
Content 2
Stacked & icon-only
stacked: icon on top, text below (vertically stacked). icon-only (on oas-tab-panel): icon-only tab without text (label provides the aria-label fallback for an accessible name).
Stacked + icon-only
Content 1: icon on top, text below.
Content 2
Content 3
Content 1: icon-only tab.
Content 2
Content 3
Indicator customization
The active indicator line (line-mode ::after) exposes CSS variables: --oas-tabs-indicator-color (color) and --oas-tabs-indicator-size (thickness). hide-indicator hides the indicator entirely.
Indicator customization
Content 1: custom indicator color and thickness.
Content 2
Content 1: indicator hidden.
Content 2
Anti-jitter on selection
reserve-selected-space: preload the selected-state text width so selected/unselected tabs keep the same width and do not shift when switching (selected uses font-weight 500 without widening the tab).
Anti-jitter on selection
Content 1: no width shift on switch.
Content 2
Content 3
Link tabs
Set href on oas-tab-panel to render that tab as an <a> link (anchor semantics: open in new window/middle-click/SEO-crawlable), with target/rel. Good for tabs-as-page-routes scenarios.
Link tabs
Content 1: I'm a link, right-click to open in a new window.
Content 2: external link in a new window.
Content 3: without href it stays a normal tab.
Pure navigation mode
hide-content: render only the tab bar without the panel area — tabs act as a nav strip (switching only emits oas-change; content/routing is the host's job).
Pure navigation mode
Only the tab bar above, no panel area; switching only fires an event, content is the host's job.
Data-driven
items: render from a JSON array (takes precedence over oas-tab-panel children when both present). Each item supports label / value / icon / badge / disabled / href / target / rel / closable / editable / iconOnly.
Data-driven
Actions slot
slot="actions" renders an action area at the right side of the tab bar. The container is fixed inside the nav, outside the scrollable tablist — when tabs overflow and scroll, the actions stay visible (same convention as the add button). With addable enabled, the actions sit after the + button. Without slotted content the container takes no space.
Actions area (visible click feedback)
Order list content.
Shipping tracking content.
tabindex passthrough
When present, tabindex is forwarded to every tab button (overriding the default roving strategy: selected button 0, others -1). Use tabindex="-1" when the host manages focus itself so tab buttons leave the Tab order (click and arrow-key switching still work); removing the attribute restores roving. Disabled tabs stay -1 and are never focusable.
tabindex=-1 (tab buttons leave Tab order)
Tab skips the tab buttons (host-managed focus scenario).
Content two.
API
oas-tabs
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
activation | Keyboard activation: auto (default, arrow keys switch immediately) / manual (arrows move focus only, Enter/Space switches — a11y manual activation) | string | auto |
active | The value of the active tab | string | — |
addable | Shows a + button at the end of the tab bar; clicking fires oas-add (the component does not add a panel) | boolean | — |
allow-deactivation | Allow clicking the active tab to deactivate it (no selection); by default clicking the active tab keeps it active | boolean | — |
animated | Selection transition + panel fade-in (animates color/border/opacity only, no layout) | boolean | — |
centered | Center the tab bar (when horizontal) | boolean | — |
closable | Shows a close × on every tab; clicking fires oas-close (the component does not remove the panel) | boolean | — |
context-menu | Right-click action menu on tabs (New / Close / Close others / Close all to the left / Close all to the right / Close all; New fires oas-add, each close fires oas-close once per target tab) | boolean | — |
hide-content | Pure navigation mode: render the tab bar without the panel area (tabs act as a nav strip; the host takes over content/routing) | boolean | — |
hide-indicator | Hide the active indicator line (the ::after underline in line mode) | boolean | — |
items | Data-driven rendering: JSON array [{ label, value, icon?, badge?, disabled?, href?, target?, rel?, closable?, editable?, iconOnly? }]; takes precedence over oas-tab-panel children when both present | string | — |
justified | Distribute tabs evenly across the full tab bar width | boolean | — |
more | Collapse overflowed tabs into a "More" dropdown instead of scroll arrows (mutually exclusive); the More button highlights when the active tab is collapsed | boolean | — |
panel-mode | Panel visibility strategy: keep (default, hidden keeps DOM) / lazy (unvisited inactive panels not mounted until first activation) / destroy (unmount inactive panel content on switch) | string | keep |
reserve-selected-space | Anti-jitter for bold selection: preload the selected-state text width so selected/unselected tabs keep the same width and do not shift on switch | boolean | — |
scroll-position | Scroll alignment when scrolling the active/added tab into view: auto (default nearest) / start / center / end | string | auto |
size | Tab size: xs/small/medium/large/xl (default medium), font-size/padding follow the step; invalid values fall back to medium with a warning | string | medium |
sortable | Tabs are drag-sortable (native HTML5 DnD); emits oas-reorder after drop (host reorders panel data accordingly, the component does not move DOM itself) | boolean | — |
stacked | Icon on top, text below (vertically stacked tabs) | boolean | — |
tab-position | Tab bar position: top (default) / left / right / bottom | string | top |
tabindex | Forwarded to every tab button, overriding roving (removing restores it; disabled stays -1) | — | — |
trigger | Switch trigger: click (default) / hover (switch on hover; disabled tabs not triggered) | string | click |
type | Style variant: line (underline, default) / card | string | line |
without-scroll-controls | Disable the overflow scroll arrows (shown by default when tabs overflow) | boolean | — |
Events
| Event | Description |
|---|---|
oas-add | The + button was clicked, detail: { label } (default new-tab label from locale; use it or customize it) |
oas-before-change | Fired before switching (cancelable), detail: { value }; host preventDefault() vetoes the switch (click/keyboard both; direct setAttribute by host does not trigger) |
oas-change | Switched, detail: { value } |
oas-close | A tab's close × was clicked, detail: { key } (key is that tab's value; the component does not remove the panel) |
oas-rename | Editable tab rename confirmed via double-click + Enter, detail: { value, label }; the component writes the new label back to the panel, host may persist |
oas-reorder | Fired after sortable drag reorder, detail: { fromIndex, toIndex }; host reorders oas-tab-panel accordingly (the component does not move DOM itself) |
oas-tab-contextmenu | Per-tab context menu signal (right-click, or Menu key / Shift+F10 on a focused tab) — cancelable; detail: { value, index, clientX, clientY, originalEvent }. Host preventDefault suppresses both the built-in menu (context-menu attribute) and the browser default menu; not fired on empty nav area. Native channel for host-built per-tab menus |
Slots
| Name | Description |
|---|---|
| default | Tab panels (oas-tab-panel) |
actions | Action area fixed at the right of the tab bar, outside the scrollable tablist (after the + button when addable; no space when empty) |
add-icon | Add button icon content (addable tabs) |
close-icon | Close button icon content (closable tabs) |
icon | Icon content for a tab item (replaces the panel's icon attribute) |
label | Label content for a tab item (not projected through the panel's default slot) |
CSS Variables
| CSS Variable | Default |
|---|---|
--oas-tabs-badge-bg | var(--oas-color-danger) |
--oas-tabs-badge-color | var(--oas-color-text-on-danger) |
--oas-tabs-indicator-color | var(--oas-color-primary) |
--oas-tabs-indicator-size | 2px |
oas-tab-panel
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
badge | Badge next to the tab title (number or text) | — | — |
disabled | Disable this tab: not focusable/clickable, aria-disabled, visually dimmed, skipped by keyboard navigation | — | — |
editable | Tab is renameable on double-click: enters an input editing state, Enter confirms (emits oas-rename) / Esc cancels | — | — |
href | Render the tab as a link: the tab becomes an <a> (anchor semantics: open in new window/middle-click/SEO-crawlable), works with target/rel | — | — |
icon | Icon name shown before the tab title (reuses the oas-icon icon set, e.g. mail) | — | — |
icon-only | Icon-only tab: renders only the icon without text (label provides the aria-label fallback for an accessible name) | — | — |
label | Tab text | — | — |
rel | Link rel (only with href, e.g. noopener) | — | — |
target | Link target (only with href, e.g. _blank) | — | — |
title | Hover hint text (full title / supplementary note) — passed through to the shadow tab button as a native hover tooltip; tabs absorb and remove it from the panel host (so the panel content area never shows a native tooltip); the items JSON title field works the same way | — | — |
value | Tab value | — | — |
Slots
| Name | Description |
|---|---|
| default | Panel content |
Keyboard: after focusing the tab list, ← / → / ↑ / ↓ cycle through tabs; with a close button focused, Enter / Space triggers close. oas-tab-panel declares the hidden attribute to hide inactive panels (content stays in the DOM).