Tabs
Switch between related views without leaving the current context.
Tabs presents one of several related panels in the same page region. Selection changes the visible panel, not the route.
'use client'import { useState } from 'react'import { Tabs, TabsContent, TabsList, TabsTrigger } from '@aboard/ui/core/tabs'export default function TabsExample() {const [value, setValue] = useState("overview")return (<Tabs className="w-full max-w-md" value={value} onValueChange={(value) => setValue(String(value))}> <TabsList aria-label={"Workspace sections"}> <TabsTrigger value="overview">{"Overview"}</TabsTrigger> <TabsTrigger value="agents">{"Agents"}</TabsTrigger> <TabsTrigger value="tasks">{"Tasks"}</TabsTrigger> </TabsList> <TabsContent value="overview" className="mt-3 text-sm text-muted-foreground">{"Research workspace: 3 active agents and 12 tasks in progress."}</TabsContent> <TabsContent value="agents" className="mt-3 text-sm text-muted-foreground">{"Researcher gathers sources. Analyst compares findings. Writer prepares the summary."}</TabsContent> <TabsContent value="tasks" className="mt-3 text-sm text-muted-foreground">{"Next: review the collected sources, compare the findings, and approve the summary."}</TabsContent></Tabs>)}When to use
Use tabs for sibling views such as a workspace overview, agent list, and task list. Use links for destinations that need independent URLs or browser history. Use radio buttons or a select when choosing a value rather than revealing content. Keep information that must be compared simultaneously outside tabs.
Import and example
The package exports four React components and the tabsListVariants styling helper. Give every trigger and its corresponding panel the same stable value; do not reuse one changing panel value for unrelated content.
These snippets assume Tailwind scans packages/ui/src/core, the full packages/ui/src/styles/reui/style-nova.css is loaded alongside shadcn/tailwind.css and tw-animate-css, and the application provides the semantic theme tokens. Render them inside a style-nova ancestor (for example, <div className="style-nova">). Copying the JSX alone does not reproduce Nova styling.
import {
Tabs, TabsContent, TabsList, TabsTrigger,
} from '@aboard/ui/core/tabs'
export function WorkspaceTabs() {
return (
<Tabs defaultValue="overview">
<TabsList aria-label="Workspace views">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="agents">Agents</TabsTrigger>
<TabsTrigger value="tasks" disabled>Tasks</TabsTrigger>
</TabsList>
<TabsContent value="overview">Workspace summary</TabsContent>
<TabsContent value="agents">No agents assigned.</TabsContent>
<TabsContent value="tasks">No tasks yet.</TabsContent>
</Tabs>
)
}For controlled selection, own the value in a client component. Do not supply value without updating it from onValueChange unless selection is intentionally fixed.
'use client'
import { useState } from 'react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@aboard/ui/core/tabs'
export function ReviewTabs() {
const [value, setValue] = useState('draft')
return (
<Tabs value={value} onValueChange={(next) => {
if (next === 'draft' || next === 'history') setValue(next)
}}>
<TabsList variant="line" aria-label="Review views">
<TabsTrigger value="draft">Draft</TabsTrigger>
<TabsTrigger value="history">History</TabsTrigger>
</TabsList>
<TabsContent value="draft" keepMounted>Draft editor region</TabsContent>
<TabsContent value="history">No revisions yet.</TabsContent>
</Tabs>
)
}Props and defaults
The wrapper uses Base UI 1.7.0 Tabs.Root.Props, Tabs.List.Props, Tabs.Tab.Props, and Tabs.Panel.Props. Derive local types with React.ComponentProps<typeof Tabs> (or the relevant exported component).
| Component / prop | Type | Default and behavior |
|---|---|---|
Tabs.value | Base UI Tabs.Tab.Value (any, including null) | Uncontrolled when omitted; null selects nothing. Prefer stable strings. |
Tabs.defaultValue | Tabs.Tab.Value | Base UI defaults to 0 and can fall back to an enabled tab. Set an explicit enabled value for predictable SSR. |
Tabs.onValueChange | (value, eventDetails) => void | No handler; details include reason and activation direction. |
Tabs.orientation | 'horizontal' | 'vertical' | 'horizontal'; forwarded to Base UI for orientation-aware keyboard behavior. With the shadcn variants loaded, vertical list styling is automatic. |
TabsList.variant | 'default' | 'line' | 'default'; changes list styling. |
TabsList.activateOnFocus | boolean | false; focus movement alone does not select a tab. |
TabsList.loopFocus | boolean | true; arrow navigation wraps. |
TabsTrigger.value / TabsContent.value | Tabs.Tab.Value | Required; matching values connect the trigger and panel. |
TabsTrigger.disabled | boolean | Enabled when omitted. |
TabsContent.keepMounted | boolean | false; inactive panel content is normally unmounted. |
Composition and accessibility
Keep TabsList and all panels within the same Tabs root. Use keepMounted when an inactive editor must retain local component state; this is not persistence across page changes. Give the list an accessible name with aria-label or aria-labelledby, and use descriptive trigger text. Base UI owns the tab, tablist, and panel relationships.
In a horizontal layout, Left/Right arrows move focus; with orientation="vertical", Up/Down arrows do so. The restored group-data-vertical/tabs variants arrange TabsList vertically; an extra flex-col override is not required with the stylesheet prerequisites above. With the default activateOnFocus={false}, Enter or Space activates the focused tab. Tab moves into or out of the tab interface rather than visiting every trigger. Preserve visible focus styling, avoid interactive controls inside triggers, and test disabled-tab navigation in the consuming screen.
State evidence and limits
The registered preview starts at overview and provides a selection control for overview, agents, and tasks. Clicking a trigger updates that same selection state. Each trigger has its own matching panel, and generated code includes all three triggers and panels with controlled state. Disabled triggers, retained panels, and vertical orientation are package APIs, not editable controls in this Preview.
Source evidence is packages/ui/src/core/tabs.tsx and apps/site/components/component-preview/interactive-samples.tsx. @aboard/ui is version 0.1.0 and pins @base-ui/react to 1.7.0; the props above follow that installed version. The recorded adaptation source is ReUI Tabs, pinned to commit 8a2c701eaf95729f238274d5ce2555a5a8bd23e7. Alongside local package imports, Aboard preserves an explicit fix: orientation is forwarded to Base UI, whereas the pinned upstream destructures it and only sets data-orientation. Restored upstream list and trigger styling uses the shadcn orientation variants.
These are source-backed descriptions, not a claim of completed browser tests or state/code parity verification. Routing, data loading, and saved selection belong to the application.
For Agents
aboard.ui.component.navigation.tabs identifies this documentation capability. The installed React composition is available to trusted application code through the imports above. It does not establish a declarative Tabs descriptor schema or prove a Workbench descriptor renderer exists. Return a proposed composition and explicit state ownership for handoff; verify the actual contribution contract before emitting a descriptor. Documentation is partial, the React API is source-backed, and declarative runtime/security/test status remains uncertain. The preview performs no Host calls or persistence.
How is this guide?