Tabs
A headless, accessible tabs primitive with automatic activation, roving tabindex, and composable Root, List, Trigger, Panel, and Content parts.
Features
- Headless
- Automatic activation
- Horizontal or vertical
- Disabled triggers
- Accessible tab panels
- Composable parts
Installation
npm install @ariaui/tabs
Examples
Automatic activation with roving tabindex: compose List, Trigger, Panel, and Content in order (each trigger maps to the Content at the same index).
Playgrounds mirror the legacy doc set—authentication forms, compact account panels, home/settings, preview/code with lucide-react icons, and billing plus a badge-styled notifications tab.
Default
Sign-in and sign-up forms with a pill tab list, checkbox, and secondary actions.
No preview output yet.
Framer Motion
A Framer Motion tabs example with a sliding active tab background.
No preview output yet.
Anatomy
import * as Tabs from "@ariaui/tabs";
export default function Example() {
return (
<Tabs.Root>
<Tabs.List>
<Tabs.Trigger />
</Tabs.List>
<Tabs.Panel>
<Tabs.Content />
</Tabs.Panel>
</Tabs.Root>
);
}
API Reference
Root
Context provider and state owner for the tabs. Manages the active value and coordinates trigger registration. Uses automatic activation — arrow keys both move focus and change the active panel.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
defaultValue | string | - |
value | string | - |
onValueChange | (value: string) => void | - |
orientation | 'horizontal' | 'vertical' | 'horizontal' |
List
Tablist container. Wraps the Trigger elements.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
| Attribute | Values |
|---|---|
| role | 'tablist' |
| aria-orientation | 'horizontal' | 'vertical' — mirrors Root's orientation |
Trigger
A tab button that activates its associated panel. Only the active Trigger is in the tab order; inactive Triggers are skipped by roving focus.
| Prop | Type | Default |
|---|---|---|
value* | string | - |
disabled | boolean | false |
asChild | boolean | false |
| Attribute | Values |
|---|---|
| role | 'tab' |
| aria-selected | 'true' when active, 'false' otherwise |
| aria-controls | ID of the associated Panel tabpanel element |
| aria-disabled | present when `disabled` |
Panel
Renders one `role="tabpanel"` container per `Content` child. Inactive panels receive the `hidden` attribute; props are forwarded only to the active panel.
| Attribute | Values |
|---|---|
| role | 'tabpanel' (on each generated panel container) |
| aria-labelledby | ID of the associated Trigger |
| hidden | present on inactive panels |
Content
Structural content slot for a single tab panel. Does not apply tabpanel semantics — those live on the surrounding Panel.
| Prop | Type | Default |
|---|---|---|
value* | string | - |
asChild | boolean | false |
Keyboard
| Shortcut | Action |
|---|---|
| → | Move focus and selection to the next tab. Wraps from last to first. |
| ← | Move focus and selection to the previous tab. Wraps from first to last. |
| Home | Move focus and selection to the first tab. |
| End | Move focus and selection to the last tab. |
| Tab | Move focus from the active Trigger into the active Panel. |
Accessibility
Tabs follows the WAI-ARIA Tabs pattern with automatic activation:
Listrenders withrole="tablist"and mirrors Root'sorientationontoaria-orientation.- Each
Triggerrenders withrole="tab", togglesaria-selectedbased on the active index, and is wired to its panel viaaria-controls. Panelrenders onerole="tabpanel"container perContentchild, witharia-labelledbypointing back at the associated Trigger. Inactive panels get the nativehiddenattribute.- Roving tabindex — only the active Trigger is in the tab order (
tabindex="0"); the rest are-1. Tab from a Trigger moves focus into the active Panel's content, not to the next Trigger. - Arrow keys move focus and change the selected tab (automatic activation).
ArrowRight/ArrowLeftwrap at the ends;HomeandEndjump to the first and last Trigger. - Disabled Triggers receive
aria-disabledand are skipped by arrow-key navigation, so focus never lands on a dead tab. - Make sure every Trigger has a visible, human-readable label — avoid icon-only triggers without
aria-label, since screen readers announce the Trigger's accessible name as the tab title.