Skip to content

Tabs ​

Overview ​

Manual or automatic tab activation with RTL-aware navigation.

Pattern: Tabs. Status: stable.

When to use ​

  • Tabbed interfaces with manual or automatic activation and tab/panel relationships.
  • Horizontal, vertical, and RTL-aware composites with disabled tabs and dynamic registration.

When not to use ​

  • Disclosure-style content where multiple panels may stay open.
  • Navigation to separate pages; use links or Navigation Menu.

Import ​

ts
import { createTabs } from 'ui-headless-runtime';

Controller creation ​

Create Tabs during component mount or setup, subscribe before rendering derived UI, and keep every cleanup returned by registrations or DOM binding.

Options ​

  • Public options: activation, id, loop, orientation, rtl, defaultValue, getValue, onValueChange, subscribeValue.
  • activation is automatic or manual; orientation is horizontal or vertical.

Snapshot ​

  • Snapshot fields: activation, controlled, focusedId, items, orientation, selectedId.
  • Each tab snapshot contains id, tabId, panelId, selected, disabled, and tabIndex.

Commands ​

  • Component commands: focus, handleKeyDown, registerTab, select.

Events ​

  • Events: beforeSelect, select, stateChange.
  • Payloads contain the selected ID and typed details; beforeSelect is cancellable.

Change reasons ​

  • Change reasons: programmatic, pointer, keyboard, focus.

Controlled mode ​

Controlled tabs keep selection in a parent store while the controller manages focus and requested changes.

Uncontrolled mode ​

Uncontrolled tabs own selected and focused tab IDs and update snapshots immediately.

DOM binding ​

  • Register every tab with its panel ID and release the registration when either side unmounts.

Required markup ​

  • Use a tablist, tab, and tabpanel structure with hidden inactive panels as appropriate.

ARIA contract ​

  • Apply aria-selected, aria-controls, aria-labelledby, and roving tabindex from the snapshot.

Keyboard interaction ​

  • ArrowRight / ArrowLeft: Move focus between horizontal tabs, respecting text direction.
  • ArrowDown / ArrowUp: Move focus between vertical tabs.
  • Home / End: Move focus to the first or last enabled tab.
  • Enter / Space: Activate the focused tab in manual mode.

Focus behavior ​

  • Automatic mode selects as focus moves; manual mode waits for Enter or Space.

Nested behavior ​

  • Nested tabsets should use independent controllers so roving focus scopes do not collide.

Cleanup ​

  • Removing the selected tab chooses the next valid tab or clears selection when none remain.

Minimal lifecycle example ​

ts
import { createTabs } from 'ui-headless-runtime';

const controller = createTabs();
const unsubscribe = controller.subscribe((snapshot) => {
  console.log(snapshot);
});

console.log(controller.getSnapshot());
unsubscribe();
controller.destroy();

The production demo loads the exact executable module from apps/demo/src/examples/tabs.ts.

Edge cases ​

  • automatic: Selection follows roving focus.
  • manual: Enter or Space commits selection.
  • vertical: Up and Down replace horizontal arrows.

Limitations ​

  • The runtime does not lazy-load panel content; consumers decide rendering policy.

API reference ​

See createTabs.

Headless behavior; consumer-owned rendering and styling.