Components
Tabs
A set of layered panels controlled by a tab list.
Installation
Choose the package when you want centralized updates, or copy the source through the registry when you want local ownership.
Install the package, import its global tokens once, then use this exact subpath.
pnpm add @constructive-io/ui@import '@constructive-io/ui/globals.css';import { Tabs } from '@constructive-io/ui/tabs';When to use
- Use Tabs to switch among a small set of peer views while keeping one view visible at a time.
- Use Collapsible for independently expandable supporting sections. Use normal navigation when each destination needs its own URL and browser history entry.
Basic usage
Keep TabsTrigger elements inside TabsList and pair each trigger value with one TabsContent value inside the same Tabs root. When render supplies a non-button trigger, set nativeButton to false.
'use client';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@constructive-io/ui/tabs';
function DatabasePanel({ title, children }: {
title: string;
children: string;
}) {
return (<div className="rounded-lg border bg-background p-4 text-sm">
<p className="text-pretty font-medium">{title}</p>
<p className="mt-1 text-pretty text-muted-foreground tabular-nums">{children}</p>
</div>);
}
export function BasicTabsDemo() {
return (<Tabs defaultValue="overview" className="w-full max-w-[420px]">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="usage">Usage</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="mt-4">
<DatabasePanel title="production-db">PostgreSQL 17 · us-east-1 · 12 tables.</DatabasePanel>
</TabsContent>
<TabsContent value="usage" className="mt-4">
<DatabasePanel title="This month">1.2M rows read · 84K rows written.</DatabasePanel>
</TabsContent>
<TabsContent value="settings" className="mt-4">
<DatabasePanel title="Access">Row-level security on · public reads disabled.</DatabasePanel>
</TabsContent>
</Tabs>);
}
Controlled and uncontrolled selection
Use defaultValue when Tabs can remember its own selection. Pass value and onValueChange when the selection must synchronize with application state.
Examples
Accessibility
- Use a concise label for each TabsTrigger and keep every trigger inside TabsList so Base UI can provide the tablist relationship.
- Match each trigger value to exactly one TabsContent value. Base UI manages arrow-key navigation, selection, and tab-panel relationships.
- A disabled tab remains discoverable but unavailable; do not use disabled tabs to hide permissions or required information.
API Reference
Constructive-specific behavior is listed here. Each part links to its inherited platform or primitive contract.
TabsRoot that manages the active tab and orientation.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | any | — | Controlled active tab value. |
| defaultValue | any | — | Initial active value in uncontrolled usage. |
| onValueChange | Base UI callback | — | Runs when the selected tab changes. |
| orientation | 'horizontal' | 'vertical' | 'horizontal' | Sets layout and arrow-key behavior. |
Also accepts Base UI Tabs props.
TabsListContainer that gives its triggers tablist semantics.
Also accepts Base UI Tabs props.
TabsTriggerInteractive tab that selects the panel with the same value.
| Prop | Type | Default | Description |
|---|---|---|---|
| value * | any | — | Value shared with its TabsContent. |
| disabled | boolean | — | Prevents selection and keyboard activation. |
| render | ReactElement | render function | — | Base UI composition API. |
| nativeButton | boolean | true | Set to false when render resolves to a non-button element. |
Also accepts Base UI Tabs props.
TabsContentPanel associated with a TabsTrigger value.
| Prop | Type | Default | Description |
|---|---|---|---|
| value * | any | — | Value shared with its TabsTrigger. |
Also accepts Base UI Tabs props.