Tabs

Free

6 visual styles (underline, pills, boxed, lifted, border-card, unstyled), dismissible and scrollable tabs, vertical layout, keyboard navigation and lazy panels.

</>sv-tabs tabsnavigationpanelsclosable
import { SvTabsComponent } from '@styloviz/tabs';

Demo & usage

Basic usage

Usage example — open the gallery for the full interactive demo.

An interactive demo for this component lives in the gallery.

Open live demo

More examples

Pills, full-width

example.htmlAngular
<sv-tabs [tabs]="tabs" tabsStyle="pills" [fullWidth]="true" activeId="overview" />

Dismissible content tabs

example.htmlAngular
<sv-tabs [tabs]="panels()" [(activeId)]="activePanel" tabsStyle="boxed" (tabClose)="dismiss($event)" />

Scrollable overflow

example.htmlAngular
<sv-tabs [tabs]="manyTabs" [(activeId)]="activeTab" [scrollable]="true" />

When to use

  • Job: local panels — owns its panels and renders no links; whether the active tab lives in the URL is your decision (compare all seven in the “Choosing a navigation pattern” guide under Guides).
  • Switching between sibling views of the same context without leaving the page.
  • Detail panes, settings sections, dashboards with multiple perspectives.
  • Letting the user dismiss a panel the author permits them to hide (`closable` + `tabClose`) — the author still owns the set.
  • Not for multi-document or workspace bars where the user opens, closes, reorders and persists tabs — that is a separate document-tabs component (`NV-06-V1`, see `docs/navigation-catalog-adr.md`), not a configuration of this one.
  • For a simple value-only toggle with no content panels, use the Segmented Control component instead.

Async states

None — this component has nothing to load, so it has no loading, empty or error state to drive.

Inputs

NameTypeDefaultDescription
tabsTabItem[][]Tab definitions in display order.
activeIdstring (two-way)''ID of the currently active tab. Use `[(activeId)]` for two-way binding.
tabsStyleTabsStyle'underline'Visual style variant.
sizeTabsSize'md'Size of the tab text and icons.
layoutTabsLayout'horizontal'Layout direction.
fullWidthbooleanfalseWhether tab buttons stretch to fill all available width. Only applicable to the `'horizontal'` layout.
scrollablebooleanfalseAllows the tab list to scroll horizontally instead of wrapping/overflowing when there are more tabs than fit the available width.
customClassstring''Additional CSS classes on the root wrapper.
activationTabsActivation | nullnullWhether the arrow keys select the tab they move to (`'automatic'`) or only move focus, leaving Enter/Space to select (`'manual'`). Prefer `'manual'` when a panel costs something to show — `lazy` panels that fetch, heavy charts, anything a keyboard user should not trigger five times on the way past. Leaving it unset resolves to `'automatic'`, and logs a dev-only warning if any tab is `lazy`.
lazyRetentionTabsLazyRetention'retain'What happens to a `lazy` panel the user switches away from: `'retain'` keeps it mounted with its state (the long-standing behaviour), `'destroy'` unmounts it and rebuilds it on the next visit. Only `lazy` tabs are affected — a panel that was never deferred is never destroyed. Choose `'destroy'` for panels that must re-read their data on every visit, or hold enough DOM that keeping several alive is the real cost; and know that it discards whatever the user typed.
urlKeystring | nullnullPut the active tab in the URL under this key, so a tab can be shared and survives a refresh. Needs `provideSvNavLocation()` at the app root; give a nested tab strip a different key.
surfaceIdstring | nullnullNames this tabset in the shared navigation-analytics stream, when a page has more than one. Ignored unless `provideSvNavTelemetry()` is wired up.

Outputs

EventPayload typeDescription
tabCloseTabItemEmitted when a closable tab's × is clicked or Delete/Backspace is pressed on it. The tab is NOT removed automatically — remove it from `tabs` yourself.
unresolvedSvUnresolvedValue<string>Emitted when `activeId` names a tab this component cannot show. Reports; never corrects, because there is no nearest tab to correct to.
activeIdChangestringEmits the new `activeId` whenever it changes — the output half of `[(activeId)]`.

Customization & theming

  • Underline, pills, boxed, lifted, border-card, or unstyled styles.
  • Horizontal or vertical orientation.
  • Optional icon, numeric badge, unread dot, and a dismiss (×) affordance per tab via `closable`.
  • Automatic horizontal scroll for long tab lists — a horizontal tablist scrolls on overflow (e.g. many tabs on a phone) with no scrollbar while the tabs fit; `scrollable` keeps tabs at their natural width instead of shrinking to fit first.
  • Lazy-rendered panels.

Best practices

  • Do: Keep tab labels to one or two words and a manageable count.
  • Do: Lazy-render heavy panels so only the active tab pays the cost.
  • Do: When using `closable`, remove the tab from your own `tabs` array on `(tabClose)` — the component never mutates it for you.
  • Don't: Don't use tabs for sequential steps — that's a Stepper.
  • Tip: Reach for `unstyled` when you need full control over the visual treatment — every tab exposes `data-active`/`data-disabled` attributes for custom CSS.

Accessibility

  • Tablist/tab/tabpanel roles with arrow-key navigation (←→/↑↓, Home, End).
  • Active tab exposes selected state; roving tabindex.
  • Active panel is focusable (tabindex 0) so keyboard users land in its content.
  • Dismissible tabs close from the keyboard with Delete or Backspace on the focused tab (announced via `aria-keyshortcuts` and a visually hidden hint). The × is a pointer-only, `aria-hidden` affordance and is not focusable, because the ARIA tablist pattern leaves no valid place for a second focusable control inside a `role="tab"`.

Feedback on Tabs

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.