Skip to content

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

SubmitCancelEnabledPending

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

Current active: a

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-add and the host appends a panel; each close fires oas-close once 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/tabs already 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/core instead — it omits the manager capability, and using these options there logs a dev hint pointing to import '@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' } }) (import setLocale from @oas-ui/i18n, en from @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

Unsaved changes (switching is blocked while checked)

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/tabs already 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/core instead — it omits the manager capability, and using these options there logs a dev hint pointing to import '@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/tabs already 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/core instead — it omits the manager capability, and using these options there logs a dev hint pointing to import '@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.

VIP Member

Content 2: custom rich-text label.

● Urgent

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

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.

Refresh

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 ​

AttributeDescriptionTypeDefault
activationKeyboard activation: auto (default, arrow keys switch immediately) / manual (arrows move focus only, Enter/Space switches — a11y manual activation)stringauto
activeThe value of the active tabstring—
addableShows a + button at the end of the tab bar; clicking fires oas-add (the component does not add a panel)boolean—
allow-deactivationAllow clicking the active tab to deactivate it (no selection); by default clicking the active tab keeps it activeboolean—
animatedSelection transition + panel fade-in (animates color/border/opacity only, no layout)boolean—
centeredCenter the tab bar (when horizontal)boolean—
closableShows a close × on every tab; clicking fires oas-close (the component does not remove the panel)boolean—
context-menuRight-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-contentPure navigation mode: render the tab bar without the panel area (tabs act as a nav strip; the host takes over content/routing)boolean—
hide-indicatorHide the active indicator line (the ::after underline in line mode)boolean—
itemsData-driven rendering: JSON array [{ label, value, icon?, badge?, disabled?, href?, target?, rel?, closable?, editable?, iconOnly? }]; takes precedence over oas-tab-panel children when both presentstring—
justifiedDistribute tabs evenly across the full tab bar widthboolean—
moreCollapse overflowed tabs into a "More" dropdown instead of scroll arrows (mutually exclusive); the More button highlights when the active tab is collapsedboolean—
panel-modePanel visibility strategy: keep (default, hidden keeps DOM) / lazy (unvisited inactive panels not mounted until first activation) / destroy (unmount inactive panel content on switch)stringkeep
reserve-selected-spaceAnti-jitter for bold selection: preload the selected-state text width so selected/unselected tabs keep the same width and do not shift on switchboolean—
scroll-positionScroll alignment when scrolling the active/added tab into view: auto (default nearest) / start / center / endstringauto
sizeTab size: xs/small/medium/large/xl (default medium), font-size/padding follow the step; invalid values fall back to medium with a warningstringmedium
sortableTabs are drag-sortable (native HTML5 DnD); emits oas-reorder after drop (host reorders panel data accordingly, the component does not move DOM itself)boolean—
stackedIcon on top, text below (vertically stacked tabs)boolean—
tab-positionTab bar position: top (default) / left / right / bottomstringtop
tabindexForwarded to every tab button, overriding roving (removing restores it; disabled stays -1)——
triggerSwitch trigger: click (default) / hover (switch on hover; disabled tabs not triggered)stringclick
typeStyle variant: line (underline, default) / cardstringline
without-scroll-controlsDisable the overflow scroll arrows (shown by default when tabs overflow)boolean—

Events ​

EventDescription
oas-addThe + button was clicked, detail: { label } (default new-tab label from locale; use it or customize it)
oas-before-changeFired before switching (cancelable), detail: { value }; host preventDefault() vetoes the switch (click/keyboard both; direct setAttribute by host does not trigger)
oas-changeSwitched, detail: { value }
oas-closeA tab's close × was clicked, detail: { key } (key is that tab's value; the component does not remove the panel)
oas-renameEditable tab rename confirmed via double-click + Enter, detail: { value, label }; the component writes the new label back to the panel, host may persist
oas-reorderFired after sortable drag reorder, detail: { fromIndex, toIndex }; host reorders oas-tab-panel accordingly (the component does not move DOM itself)
oas-tab-contextmenuPer-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 ​

NameDescription
defaultTab panels (oas-tab-panel)
actionsAction area fixed at the right of the tab bar, outside the scrollable tablist (after the + button when addable; no space when empty)
add-iconAdd button icon content (addable tabs)
close-iconClose button icon content (closable tabs)
iconIcon content for a tab item (replaces the panel's icon attribute)
labelLabel content for a tab item (not projected through the panel's default slot)

CSS Variables ​

CSS VariableDefault
--oas-tabs-badge-bgvar(--oas-color-danger)
--oas-tabs-badge-colorvar(--oas-color-text-on-danger)
--oas-tabs-indicator-colorvar(--oas-color-primary)
--oas-tabs-indicator-size2px

oas-tab-panel ​

Attributes ​

AttributeDescriptionTypeDefault
badgeBadge next to the tab title (number or text)——
disabledDisable this tab: not focusable/clickable, aria-disabled, visually dimmed, skipped by keyboard navigation——
editableTab is renameable on double-click: enters an input editing state, Enter confirms (emits oas-rename) / Esc cancels——
hrefRender the tab as a link: the tab becomes an <a> (anchor semantics: open in new window/middle-click/SEO-crawlable), works with target/rel——
iconIcon name shown before the tab title (reuses the oas-icon icon set, e.g. mail)——
icon-onlyIcon-only tab: renders only the icon without text (label provides the aria-label fallback for an accessible name)——
labelTab text——
relLink rel (only with href, e.g. noopener)——
targetLink target (only with href, e.g. _blank)——
titleHover 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——
valueTab value——

Slots ​

NameDescription
defaultPanel 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).

Released under the MIT OR Apache-2.0 License.