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.
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 or blur cancels.
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.
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
API
oas-tabs
| 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 | — |
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 |
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 | — |
| 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) |
| Name | Description |
|---|---|
| default | — |
oas-tab-panel
| 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) | — | — |
value | Tab value | — | — |
| Name | Description |
|---|---|
| default | — |
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).