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.

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 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.

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

API

oas-tabs

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
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
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
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)
NameDescription
default

oas-tab-panel

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)
valueTab value
NameDescription
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).

Released under the MIT OR Apache-2.0 License.