Tabs
import { Tabs } from '@absoluteui/components/tabs';import '@absoluteui/components/tabs/style.css';Using Angular? See Tabs for Angular.
What it does
Section titled “What it does”Switch between peer views of related content. Tabs implements the WAI-ARIA APG Tabs pattern: a labelled tablist of native buttons with a roving tabindex, and one tabpanel per tab. Activation is automatic (select on focus) or manual (Enter/Space), orientation is horizontal or vertical, arrow keys are mirrored in RTL, and a horizontal tablist that overflows scrolls with the selected tab kept in view.
When to use
Section titled “When to use”- Switching between peer views of related content in the same context, such as Overview, Activity, and Settings.
- Reducing page length when users need only one section at a time.
When not to use
Section titled “When not to use”- Sequential steps: use a stepper or wizard.
- Navigating between pages or routes: use links (navigation bar, breadcrumb).
- Content users need to compare side by side.
- Many sections users read in order: use headings or an accordion.
Examples
Section titled “Examples”Account tabs with text content
Section titled “Account tabs with text content”import { Tabs } from '@absoluteui/components/tabs';
const host = document.createElement('div');document.body.append(host);Tabs.create(host, { label: 'Account', tabs: [ { id: 'overview', label: 'Overview', content: 'Plan, usage, and contacts.' }, { id: 'billing', label: 'Billing', icon: 'info', content: 'Invoices and payment methods.' }, { id: 'audit', label: 'Audit log', disabled: true }, ],});Manual activation with lazily filled panels
Section titled “Manual activation with lazily filled panels”import { Tabs, type TabsChangeDetail } from '@absoluteui/components/tabs';
const host = document.createElement('div');document.body.append(host);const tabs = Tabs.create(host, { label: 'Reports', activation: 'manual', tabs: [ { id: 'daily', label: 'Daily' }, { id: 'weekly', label: 'Weekly' }, ],});const fill = (id: string): void => { const panel = tabs.panel(id); if (panel && panel.childElementCount === 0) { const table = document.createElement('table'); panel.append(table); }};fill(tabs.value);host.addEventListener('aui-change', (event) => { fill((event as CustomEvent<TabsChangeDetail>).detail.value);});Vertical settings navigation
Section titled “Vertical settings navigation”import { Tabs } from '@absoluteui/components/tabs';
const host = document.createElement('div');document.body.append(host);const settings = Tabs.create(host, { label: 'Settings', orientation: 'vertical', tabs: [ { id: 'profile', label: 'Profile' }, { id: 'security', label: 'Security' }, { id: 'notifications', label: 'Notifications' }, ], value: 'security',});settings.select('notifications');import { Tabs, type TabsChangeDetail } from '@absoluteui/components/tabs';import '@absoluteui/components/tabs/style.css';
const host = document.querySelector<HTMLElement>('#report-tabs');if (host) { const tabs = Tabs.create(host, { label: 'Reports', tabs: [{ id: 'daily', label: 'Daily' }, { id: 'weekly', label: 'Weekly' }] }); host.addEventListener('aui-change', (event) => { const panel = tabs.panel((event as CustomEvent<TabsChangeDetail>).detail.value); if (panel && panel.childElementCount === 0) panel.append(document.createElement('table')); });}More usage
Section titled “More usage”import { Tabs } from '@absoluteui/components/tabs';
const host = document.createElement('div');document.body.append(host);Tabs.create(host, { label: 'Account', tabs: [ { id: 'overview', label: 'Overview', content: 'Plan, usage, and contacts.' }, { id: 'billing', label: 'Billing', content: 'Invoices and payment methods.' }, ],});Anatomy
Section titled “Anatomy”<div class="aui-tabs aui-tabs--horizontal"> (consumer host) <div class="aui-tabs__list" role="tablist" aria-label aria-orientation> <button class="aui-tabs__tab" role="tab" id aria-selected aria-controls tabindex> <svg class="aui-icon" aria-hidden="true"> (optional) <span class="aui-tabs__label">Label</span> </button> ... </div> <div class="aui-tabs__panels"> <div class="aui-tabs__panel" role="tabpanel" id aria-labelledby tabindex="0"> (selected) <div class="aui-tabs__panel" role="tabpanel" hidden> (inactive) </div></div>API reference
Section titled “API reference”Factory
Section titled “Factory”Tabs.create(host: HTMLElement, options: TabsOptions): TabsGlobal configuration key: tabs (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
label |
string |
Accessible name of the tablist (aria-label). Required. | |
tabs |
readonly { id: string; label: string; icon?: IconRef; disabled?: boolean; content?: Node | string }[] |
Tabs in order. Ids must be unique; they are consumer keys, not DOM ids. Required. | |
value |
string |
Selected tab id. Default: the first enabled tab. | |
activation |
'automatic' | 'manual' |
"automatic" |
automatic selects on focus; manual needs Enter or Space. Globally configurable. |
orientation |
'horizontal' | 'vertical' |
"horizontal" |
Layout and arrow keys (Left/Right or Up/Down). Globally configurable. |
direction |
'ltr' | 'rtl' |
"ltr" |
Mirrors Left/Right in rtl. An ancestor dir=rtl also mirrors. Globally configurable. Inherited from common configuration. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
host |
HTMLElement |
Consumer host; its children are owned by the component. |
tablist |
HTMLDivElement |
The role=tablist element. |
value |
string |
Selected tab id. |
options |
Readonly<TabsOptions> |
Current instance options. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
panel(id: string): HTMLDivElement | null |
The tabpanel for a tab id, for consumers to fill. |
select(id: string): void |
Select programmatically (no events). |
update(options: Partial<TabsOptions>): void |
Merge and apply options. Panels are kept by id. |
destroy(): void |
Remove rendering, listeners, observers, and subscriptions. |
Events
Section titled “Events”| Event | Detail | Bubbles | Cancelable | Description |
|---|---|---|---|---|
aui-change |
{ value: string; previous: string } |
Yes | No | The user selected a different tab. |
Content areas
Section titled “Content areas”| Area | Description |
|---|---|
panel |
One tabpanel per tab: tabs.panel(id), or the tab’s content option. |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-tabs |
Host. |
aui-tabs--horizontal | --vertical |
Orientation modifiers on the host. |
aui-tabs__list |
role=tablist. |
aui-tabs__tab |
role=tab button. |
aui-tabs__label |
Tab label text. |
aui-tabs__panels |
Panel container. |
aui-tabs__panel |
role=tabpanel. |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-border-width | --aui-border-width-strong | --aui-color-border | --aui-color-focus-ring | --aui-color-hover | --aui-color-primary | --aui-color-text | --aui-color-text-disabled | --aui-color-text-muted | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-weight-semibold | --aui-icon-size-sm | --aui-line-height-normal | --aui-space-1 | --aui-space-2 | --aui-space-4 | --aui-touch-target-min
TypeScript definitions
Section titled “TypeScript definitions”type TabsActivation = 'automatic' | 'manual';type TabsOrientation = 'horizontal' | 'vertical';interface TabItem { id: string; label: string; icon?: IconRef; disabled?: boolean; content?: Node | string }interface TabsChangeDetail { value: string; previous: string }Accessibility
Section titled “Accessibility”Pattern: WAI-ARIA APG Tabs pattern (automatic and manual activation), with native buttons as tabs.
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| Tab | Focus outside the tablist | Moves focus to the selected tab (single tab stop). |
| Tab | Tab focused | Moves focus into the active panel (or its first focusable element). |
| ArrowRight / ArrowLeft | Horizontal tablist | Next / previous enabled tab, wrapping; mirrored in RTL. Automatic mode also selects it. |
| ArrowDown / ArrowUp | Vertical tablist | Next / previous enabled tab, wrapping. Automatic mode also selects it. |
| Home / End | Tab focused | First / last enabled tab. Automatic mode also selects it. |
| Enter / Space | Tab focused (manual activation) | Selects the focused tab. |
| Element | Role |
|---|---|
| tab container | tablist |
| <button> | tab |
| panel | tabpanel |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-label |
tablist | label option |
aria-orientation |
tablist | horizontal | vertical |
aria-selected |
tab | true on the selected tab, false otherwise |
aria-controls |
tab | id of its tabpanel |
aria-labelledby |
tabpanel | id of its tab |
tabindex |
tabpanel | 0 when the panel contains no focusable element |
disabled |
tab | Native disabled for disabled tabs (skipped by arrow keys) |
One tab stop on the selected tab. In manual mode focus can move without selecting; when focus leaves the tablist, the tab stop returns to the selected tab. Re-rendering never moves or blurs the focused tab.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”| Criterion | Level | Note |
|---|---|---|
| 1.3.1 Info and Relationships | A | |
| 1.4.1 Use of Color | A | |
| 2.1.1 Keyboard | A | |
| 2.4.3 Focus Order | A | |
| 2.4.7 Focus Visible | AA | |
| 4.1.2 Name, Role, Value | A |
Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- Selection is conveyed by aria-selected, an indicator bar, and heavier weight (not color alone).
- Inactive panels use the hidden attribute, so they are removed from the accessibility tree.
- Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.
Behavior
Section titled “Behavior”States
Section titled “States”default, hover, focus-visible, selected, disabled, overflowing (scrollable tablist).
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
| tab A selected | click tab B, or arrow to B (automatic) | B selected; aui-change |
| tab A selected | arrow to B (manual) | A still selected; B focused with the tab stop |
| B focused (manual) | Enter / Space | B selected; aui-change |
| B focused (manual) | focus leaves the tablist | tab stop returns to the selected tab |
| any | select(id) / update({ value }) |
selection changes; no event |
selected tab removed by update({ tabs }) |
re-render | first enabled tab selected; no event |
Accessibility notes
Section titled “Accessibility notes”- Selection is shown by an indicator bar and semibold weight, not by color alone (WCAG 1.4.1).
- Re-rendering never re-inserts a focused tab, so focus is not lost during automatic activation.
- Icons are decorative; the label text is the accessible name.
- Manual screen-reader verification is pending (see Known limitations).
Responsive behavior
Section titled “Responsive behavior”The horizontal tablist never wraps. It scrolls horizontally (overflow-x: auto, thin scrollbar), and after every selection the tablist, never the page, is scrolled so the selected tab is fully visible. Tabs keep the minimum touch-target height.
Right-to-left
Section titled “Right-to-left”Logical properties throughout. ArrowLeft/ArrowRight are mirrored when direction is 'rtl' or when the host inherits dir="rtl". The vertical indicator is on the inline-start edge.
Theming
Section titled “Theming”Colors come from role tokens only. Verified in the light, dark, and high-contrast themes. In forced-colors mode the selected indicator uses Highlight and disabled tabs use GrayText.
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { tabs: { activation: 'manual' } } });Changes apply to existing instances immediately.
Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All options are dynamic.
Each tab accepts an optional decorative icon (IconRef). There are no built-in icon slots.
Security
Section titled “Security”Labels and string content use textContent; HTML strings are never parsed. Node content is inserted as provided, so the consumer owns its safety. CSP-safe: no inline styles or handlers.
Data handling
Section titled “Data handling”No persistence, network access, or telemetry.
Performance
Section titled “Performance”Three listeners on the tablist (click, keydown, focusout), one MutationObserver on the panel container, and one configuration subscription per instance. Re-renders reuse tab and panel elements by id. Budget: under 2 ms for Tabs.create with 20 tabs.
Supported data scale
Section titled “Supported data scale”Tested with 20 tabs in an overflowing tablist. Beyond about 10 tabs, consider a different navigation pattern.
Edge cases
Section titled “Edge cases”- If every tab is disabled, the first tab is selected and the tablist has no tab stop.
- A selected tab that becomes disabled stays selected; the tab stop moves to the first enabled tab.
- Removing the selected tab through
update({ tabs })selects the first enabled tab without an event. - Consumer ids can be any non-empty string; DOM ids are generated separately.
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
TABS_OPTIONS |
configuration | Missing label, empty tabs, duplicate or empty ids, empty tab labels, or a value/select(id) that is not a tab id. |
CFG_INVALID_VALUE |
configuration | Invalid activation, orientation, or direction. |
A failed construction leaves no rendering, listeners, or subscriptions behind. A failed update() keeps the previous state.
Known limitations
Section titled “Known limitations”- Panels are always in the DOM; lazy loading is the consumer’s job (for example on
aui-change). - No closable, reorderable, or user-added tabs.
- No scroll buttons for an overflowing tablist.
- Screen-reader behavior has not yet been manually verified with NVDA, JAWS, and VoiceOver.
Unsupported scenarios
Section titled “Unsupported scenarios”- HTML strings as labels or content.
- Tabs that navigate to separate URLs.