Tabs
An options-driven tabs component with multiple variants and orientation support
Installation
Usage
Basic Usage
The basic tabs implementation with uncontrolled state. Tabs is options-driven: declare each tab with value, label and content.
"use client";import { Tabs } from "@/ui";export default function TabsBasicDemo() { return ( <Tabs defaultValue="account" options={[ { value: "account", label: "Account", content: "Manage your account settings and preferences.", }, { value: "password", label: "Password", content: "Change your password and security settings.", }, { value: "notifications", label: "Notifications", content: "Configure your notification preferences.", }, ]} /> );}Variants
Switch between line and button variants for different visual styles.
Line Variant (Default)
Button Variant
"use client";import { Tabs } from "@/ui";export default function TabsVariantDemo() { return ( <div className="flex flex-col gap-8 w-md"> <div> <h3 className="text-sm font-medium mb-3">Line Variant (Default)</h3> <Tabs defaultValue="tab1" variant="line" options={[ { value: "tab1", label: "Tab One", content: "Content for tab one with line variant.", }, { value: "tab2", label: "Tab Two", content: "Content for tab two with line variant.", }, { value: "tab3", label: "Tab Three", content: "Content for tab three with line variant.", }, ]} /> </div> <div> <h3 className="text-sm font-medium mb-3">Button Variant</h3> <Tabs defaultValue="tab1" variant="button" options={[ { value: "tab1", label: "Tab One", content: "Content for tab one with button variant.", }, { value: "tab2", label: "Tab Two", content: "Content for tab two with button variant.", }, { value: "tab3", label: "Tab Three", content: "Content for tab three with button variant.", }, ]} /> </div> </div> );}Orientation
Support for both horizontal and vertical tab layouts.
Horizontal (Default)
Vertical
"use client";import { Tabs } from "@/ui";export default function TabsOrientationDemo() { return ( <div className="flex flex-col gap-8 w-md"> <div> <h3 className="text-sm font-medium mb-3">Horizontal (Default)</h3> <Tabs defaultValue="horizontal1" orientation="horizontal" options={[ { value: "horizontal1", label: "First", content: "Horizontal tab content - first tab.", }, { value: "horizontal2", label: "Second", content: "Horizontal tab content - second tab.", }, { value: "horizontal3", label: "Third", content: "Horizontal tab content - third tab.", }, ]} /> </div> <div> <h3 className="text-sm font-medium mb-3">Vertical</h3> <Tabs defaultValue="vertical1" orientation="vertical" options={[ { value: "vertical1", label: "First", content: "Vertical tab content - first tab.", }, { value: "vertical2", label: "Second", content: "Vertical tab content - second tab.", }, { value: "vertical3", label: "Third", content: "Vertical tab content - third tab.", }, ]} /> </div> </div> );}Controlled Mode
Manage the active tab externally with controlled state.
Current tab: tab1
"use client";import * as React from "react";import { Tabs } from "@/ui";export default function TabsControlledDemo() { const [value, setValue] = React.useState<string>("tab1"); return ( <div className="flex flex-col gap-4"> <Tabs value={value} onValueChange={setValue} options={[ { value: "tab1", label: "Tab One", content: "Controlled tab one content.", }, { value: "tab2", label: "Tab Two", content: "Controlled tab two content.", }, { value: "tab3", label: "Tab Three", content: "Controlled tab three content.", }, ]} /> <p className="text-sm text-muted-foreground">Current tab: {value}</p> </div> );}API Reference
High-level Components
Tabs
Options-driven container managing the active value and rendering TabsList, TabsTrigger and TabsContent parts with all state passed as parameters — no context.
| Prop | Type | Default | Description |
|---|---|---|---|
options | TabsOptionConfig[] | - | Tab definitions |
defaultValue | string | - | Initial active tab value (uncontrolled mode). Falls back to the first option's value |
value | string | - | Active tab value (controlled mode) |
onValueChange | (value: string) => void | - | Callback when active tab changes |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Layout orientation |
variant | 'button' | 'line' | 'line' | Visual style variant |
className | ClassNameValue | - | Custom CSS class, applied to the root |
...props | React.ComponentProps<"div"> | - | Supports all native div props (except className) |
TabsOptionConfig
| Property | Type | Description |
|---|---|---|
value | string | Unique identifier for the tab |
label | React.ReactNode | Content of the tab trigger |
content | React.ReactNode | Content of the tab panel |
disabled | boolean | Disable the tab |
Composable Components
TabsList
Container for the tab trigger buttons. Communicates only through parameters.
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | 'horizontal' | 'vertical' | 'horizontal' | Layout orientation |
autoScroll | boolean | false | Renders prev/next scroll buttons and hides the scrollbar (horizontal only) |
activeValue | string | - | Currently active value; scrolls the active trigger into view when it changes |
className | ClassNameValue | - | Custom CSS class |
...props | React.ComponentProps<"div"> | - | Supports all native div props (except className) |
TabsTrigger
Individual tab button. Receives its active state and change callback purely as parameters.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Unique identifier for the tab |
active | boolean | false | Whether the tab is active, drives data-state and aria-selected |
onValueChange | (value: string) => void | - | Called with the tab's own value on click or Enter/Space |
variant | 'button' | 'line' | 'line' | Visual style variant |
disabled | boolean | false | Disable the tab |
className | ClassNameValue | - | Custom CSS class |
...props | React.ComponentProps<"button"> | - | Supports all native button props (except className) |
TabsContent
Panel rendered when active is true; returns null otherwise. Wrap content in React.lazy() to defer the import until the tab becomes active.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Unique identifier, used to derive the panel id and aria-labelledby |
active | boolean | false | Whether the panel is visible |
className | ClassNameValue | - | Custom CSS class |
...props | React.ComponentProps<"div"> | - | Supports all native div props (except className) |
Lazy Loading
TabsContent returns null while inactive, so wrapping the content in lazy() defers the import:
"use client";
import { lazy, Suspense } from "react";
import { Tabs } from "@/ui";
const LazySettingsPanel = lazy(() => import("./lazy-panel"));
export default function TabsLazyDemo() {
return (
<Tabs
defaultValue="profile"
options={[
{ value: "profile", label: "Profile", content: "Profile content" },
{
value: "settings",
label: "Settings",
content: (
<Suspense fallback={null}>
<LazySettingsPanel />
</Suspense>
),
},
]}
/>
);
}