Tabs
A component for toggling between related panels on the same page. Built with Base UI and Tailwind CSS. Copy-paste ready.
Tab 1 content
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/ui/tabs";
export default function Particle() {
return (
<Tabs defaultValue="tab-1">
<TabsList>
<TabsTab value="tab-1">Tab 1</TabsTab>
<TabsTab value="tab-2">Tab 2</TabsTab>
<TabsTab value="tab-3">Tab 3</TabsTab>
</TabsList>
<TabsPanel value="tab-1">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 1 content
</p>
</TabsPanel>
<TabsPanel value="tab-2">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 2 content
</p>
</TabsPanel>
<TabsPanel value="tab-3">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 3 content
</p>
</TabsPanel>
</Tabs>
);
}
Installation
pnpm dlx shadcn@latest add @cnippet/tabs
Usage
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/ui/tabs"<Tabs defaultValue="tab-1">
<TabsList>
<TabsTab value="tab-1">Tab 1</TabsTab>
<TabsTab value="tab-2">Tab 2</TabsTab>
<TabsTab value="tab-3">Tab 3</TabsTab>
</TabsList>
<TabsPanel value="tab-1">Tab 1 content</TabsPanel>
<TabsPanel value="tab-2">Tab 2 content</TabsPanel>
<TabsPanel value="tab-3">Tab 3 content</TabsPanel>
</Tabs>Examples
Underline Variant
Replaces the default pill indicator with an animated underline beneath the active tab for a lighter visual style.
Tab 1 content
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/ui/tabs";
export default function Particle() {
return (
<Tabs defaultValue="tab-1">
<div className="border-b">
<TabsList variant="underline">
<TabsTab value="tab-1">Tab 1</TabsTab>
<TabsTab value="tab-2">Tab 2</TabsTab>
<TabsTab value="tab-3">Tab 3</TabsTab>
</TabsList>
</div>
<TabsPanel value="tab-1">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 1 content
</p>
</TabsPanel>
<TabsPanel value="tab-2">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 2 content
</p>
</TabsPanel>
<TabsPanel value="tab-3">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 3 content
</p>
</TabsPanel>
</Tabs>
);
}
Vertical Orientation
Renders tab triggers in a vertical column on the left with panels to the right, suited for settings or sidebar navigation.
Tab 1 content
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/ui/tabs";
export default function Particle() {
return (
<Tabs className="w-full" defaultValue="tab-1" orientation="vertical">
<TabsList>
<TabsTab value="tab-1">Tab 1</TabsTab>
<TabsTab value="tab-2">Tab 2</TabsTab>
<TabsTab value="tab-3">Tab 3</TabsTab>
</TabsList>
<TabsPanel value="tab-1">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 1 content
</p>
</TabsPanel>
<TabsPanel value="tab-2">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 2 content
</p>
</TabsPanel>
<TabsPanel value="tab-3">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 3 content
</p>
</TabsPanel>
</Tabs>
);
}
Underline Vertical
Combines the underline indicator style with vertical orientation, placing the animated underline on the active tab's side edge.
Tab 1 content
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/ui/tabs";
export default function Particle() {
return (
<Tabs
className="w-full flex-row"
defaultValue="tab-1"
orientation="vertical"
>
<div className="border-s">
<TabsList variant="underline">
<TabsTab value="tab-1">Tab 1</TabsTab>
<TabsTab value="tab-2">Tab 2</TabsTab>
<TabsTab value="tab-3">Tab 3</TabsTab>
</TabsList>
</div>
<TabsPanel value="tab-1">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 1 content
</p>
</TabsPanel>
<TabsPanel value="tab-2">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 2 content
</p>
</TabsPanel>
<TabsPanel value="tab-3">
<p className="p-4 text-center text-muted-foreground text-xs">
Tab 3 content
</p>
</TabsPanel>
</Tabs>
);
}
In Card
Embeds the tab list inside a Card header with an input or action in the same row for a compact dashboard widget layout.
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
export function Pattern() {
return (
<div className="flex w-full max-w-xs flex-col gap-6">
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Account</CardTitle>
<CardDescription className="text-sm">
Update your account information.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-sm" htmlFor="name">
Name
</Label>
<Input className="h-9" defaultValue="Sarah Johnson" id="name" />
</div>
<div className="space-y-2">
<Label className="text-sm" htmlFor="username">
Username
</Label>
<Input className="h-9" defaultValue="@sarahj" id="username" />
</div>
</CardContent>
<CardFooter className="pt-3">
<Button size="sm">Save changes</Button>
</CardFooter>
</Card>
</TabsContent>
<TabsContent value="password">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Password</CardTitle>
<CardDescription className="text-sm">
Change your password here.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-sm" htmlFor="current">
Current password
</Label>
<Input className="h-9" id="current" type="password" />
</div>
<div className="space-y-2">
<Label className="text-sm" htmlFor="new">
New password
</Label>
<Input className="h-9" id="new" type="password" />
</div>
</CardContent>
<CardFooter className="pt-3">
<Button size="sm">Update password</Button>
</CardFooter>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
Line Style
A clean underline-style tab variant with a bottom border indicator for a minimal, text-forward presentation.
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
export function Pattern() {
return (
<div className="flex w-full max-w-xs flex-col gap-6">
<Tabs defaultValue="account">
<TabsList className="mb-3.5 w-full" variant="underline">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
<TabsContent value="account">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Account</CardTitle>
<CardDescription className="text-sm">
Update your account information.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-sm" htmlFor="underline-name">
Name
</Label>
<Input
className="h-9"
defaultValue="Alex Chen"
id="underline-name"
/>
</div>
<div className="space-y-2">
<Label className="text-sm" htmlFor="underline-email">
Email
</Label>
<Input
className="h-9"
defaultValue="alex.chen@example.com"
id="underline-email"
type="email"
/>
</div>
</CardContent>
<CardFooter className="pt-3">
<Button size="sm">Save changes</Button>
</CardFooter>
</Card>
</TabsContent>
<TabsContent value="password">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Password</CardTitle>
<CardDescription className="text-sm">
Change your password here.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-sm" htmlFor="underline-current">
Current password
</Label>
<Input className="h-9" id="underline-current" type="password" />
</div>
<div className="space-y-2">
<Label className="text-sm" htmlFor="underline-new">
New password
</Label>
<Input className="h-9" id="underline-new" type="password" />
</div>
</CardContent>
<CardFooter className="pt-3">
<Button size="sm">Update password</Button>
</CardFooter>
</Card>
</TabsContent>
<TabsContent value="settings">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Settings</CardTitle>
<CardDescription className="text-sm">
Manage your preferences.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-sm" htmlFor="underline-theme">
Theme
</Label>
<Input
className="h-9"
defaultValue="Light"
id="underline-theme"
/>
</div>
<div className="space-y-2">
<Label className="text-sm" htmlFor="underline-language">
Language
</Label>
<Input
className="h-9"
defaultValue="English"
id="underline-language"
/>
</div>
</CardContent>
<CardFooter className="pt-3">
<Button size="sm">Save settings</Button>
</CardFooter>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
Vertical Orientation
A vertically oriented tab layout with panel content rendered beside the tab list for multi-section navigation.
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
export function Pattern() {
return (
<div className="w-full max-w-lg">
<Tabs className="gap-5" defaultValue="account" orientation="vertical">
<TabsList className="h-fit w-40 shrink-0">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
<TabsContent value="account">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Account</CardTitle>
<CardDescription className="text-sm">
Update your account information.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-sm" htmlFor="vertical-name">
Name
</Label>
<Input
className="h-9"
defaultValue="Emma Wilson"
id="vertical-name"
/>
</div>
<div className="space-y-2">
<Label className="text-sm" htmlFor="vertical-email">
Email
</Label>
<Input
className="h-9"
defaultValue="emma.wilson@example.com"
id="vertical-email"
type="email"
/>
</div>
</CardContent>
<CardFooter className="pt-3">
<Button size="sm">Save changes</Button>
</CardFooter>
</Card>
</TabsContent>
<TabsContent value="password">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Password</CardTitle>
<CardDescription className="text-sm">
Change your password here.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-sm" htmlFor="vertical-current">
Current password
</Label>
<Input className="h-9" id="vertical-current" type="password" />
</div>
<div className="space-y-2">
<Label className="text-sm" htmlFor="vertical-new">
New password
</Label>
<Input className="h-9" id="vertical-new" type="password" />
</div>
</CardContent>
<CardFooter className="pt-3">
<Button size="sm">Update password</Button>
</CardFooter>
</Card>
</TabsContent>
<TabsContent value="settings">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Settings</CardTitle>
<CardDescription className="text-sm">
Manage your preferences.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-sm" htmlFor="vertical-theme">
Theme
</Label>
<Input
className="h-9"
defaultValue="Light"
id="vertical-theme"
/>
</div>
<div className="space-y-2">
<Label className="text-sm" htmlFor="vertical-language">
Language
</Label>
<Input
className="h-9"
defaultValue="English"
id="vertical-language"
/>
</div>
</CardContent>
<CardFooter className="pt-3">
<Button size="sm">Save settings</Button>
</CardFooter>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
Line Vertical
Underline indicator combined with vertical tab orientation for a refined side-navigation look.
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
export function Pattern() {
return (
<div className="flex w-full max-w-2xl flex-col gap-6">
<Tabs className="gap-5" defaultValue="account" orientation="vertical">
<TabsList className="h-fit w-40 shrink-0" variant="underline">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
<TabsContent value="account">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Account</CardTitle>
<CardDescription className="text-sm">
Update your account information.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-sm" htmlFor="underline-vertical-name">
Name
</Label>
<Input
className="h-9"
defaultValue="Michael Brown"
id="underline-vertical-name"
/>
</div>
<div className="space-y-2">
<Label className="text-sm" htmlFor="underline-vertical-email">
Email
</Label>
<Input
className="h-9"
defaultValue="michael.brown@example.com"
id="underline-vertical-email"
type="email"
/>
</div>
</CardContent>
<CardFooter className="pt-3">
<Button size="sm">Save changes</Button>
</CardFooter>
</Card>
</TabsContent>
<TabsContent value="password">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Password</CardTitle>
<CardDescription className="text-sm">
Change your password here.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-sm" htmlFor="underline-vertical-current">
Current password
</Label>
<Input
className="h-9"
id="underline-vertical-current"
type="password"
/>
</div>
<div className="space-y-2">
<Label className="text-sm" htmlFor="underline-vertical-new">
New password
</Label>
<Input
className="h-9"
id="underline-vertical-new"
type="password"
/>
</div>
</CardContent>
<CardFooter className="pt-3">
<Button size="sm">Update password</Button>
</CardFooter>
</Card>
</TabsContent>
<TabsContent value="settings">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Settings</CardTitle>
<CardDescription className="text-sm">
Manage your preferences.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-sm" htmlFor="underline-vertical-theme">
Theme
</Label>
<Input
className="h-9"
defaultValue="Light"
id="underline-vertical-theme"
/>
</div>
<div className="space-y-2">
<Label
className="text-sm"
htmlFor="underline-vertical-language"
>
Language
</Label>
<Input
className="h-9"
defaultValue="English"
id="underline-vertical-language"
/>
</div>
</CardContent>
<CardFooter className="pt-3">
<Button size="sm">Save settings</Button>
</CardFooter>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
With Icons
Adds a leading icon to each tab trigger for quicker visual identification alongside the label text.
import { BarChart3Icon, LayoutDashboardIcon, SettingsIcon } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
export function Pattern() {
return (
<div className="flex w-full max-w-md flex-col gap-6">
<Tabs defaultValue="overview">
<TabsList className="w-full">
<TabsTrigger value="overview">
<LayoutDashboardIcon className="size-4" />
Overview
</TabsTrigger>
<TabsTrigger value="analytics">
<BarChart3Icon className="size-4" />
Analytics
</TabsTrigger>
<TabsTrigger value="settings">
<SettingsIcon className="size-4" />
Settings
</TabsTrigger>
</TabsList>
<TabsContent value="overview">
<Card>
<CardContent>Overview dashboard content goes here.</CardContent>
</Card>
</TabsContent>
<TabsContent value="analytics">
<Card>
<CardContent>Analytics charts and metrics.</CardContent>
</Card>
</TabsContent>
<TabsContent value="settings">
<Card>
<CardContent>Application settings and preferences.</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
With Badge Counts
Appends a numeric Badge to each tab label to show item counts such as unread messages or pending tasks.
Active Projects
8 projects are currently in progress across your workspace.
import {
BuildingIcon,
CheckSquareIcon,
FileTextIcon,
FolderIcon,
UserIcon,
UsersIcon,
ZapIcon,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
const _plans = [
{
description: "For individuals and small projects",
icon: <UserIcon className="size-5" />,
id: "starter",
name: "Starter",
price: "$9",
tablerIcon: "IconUser",
},
{
description: "For growing teams and businesses",
icon: <ZapIcon className="size-5" />,
id: "pro",
name: "Pro",
price: "$29",
tablerIcon: "IconBolt",
},
{
description: "For large organizations",
icon: <BuildingIcon className="size-5" />,
id: "enterprise",
name: "Enterprise",
price: "$99",
tablerIcon: "IconBuilding",
},
];
export function Pattern() {
return (
<div className="flex w-full max-w-lg flex-col gap-6">
<Tabs className="gap-5" defaultValue="projects" orientation="vertical">
<TabsList className="w-48 shrink-0">
<TabsTrigger className="justify-start gap-2" value="projects">
<FolderIcon className="size-4" />
Projects
<Badge className="ml-auto" size="sm" variant="secondary">
8
</Badge>
</TabsTrigger>
<TabsTrigger className="justify-start gap-2" value="tasks">
<CheckSquareIcon className="size-4" />
Tasks
<Badge className="ml-auto" size="sm">
24
</Badge>
</TabsTrigger>
<TabsTrigger className="justify-start gap-2" value="team">
<UsersIcon className="size-4" />
Team
</TabsTrigger>
<TabsTrigger className="justify-start gap-2" value="reports">
<FileTextIcon className="size-4" />
Reports
</TabsTrigger>
</TabsList>
<TabsContent value="projects">
<Card>
<CardContent>
<h3 className="mb-2 font-semibold text-foreground">
Active Projects
</h3>
<p>8 projects are currently in progress across your workspace.</p>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="tasks">
<Card>
<CardContent>
<h3 className="mb-2 font-semibold text-foreground">
Pending Tasks
</h3>
<p>24 tasks need your attention this week.</p>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="team">
<Card>
<CardContent>
<h3 className="mb-2 font-semibold text-foreground">
Team Members
</h3>
<p>Manage your team and their access permissions.</p>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="reports">
<Card>
<CardContent>
<h3 className="mb-2 font-semibold text-foreground">Reports</h3>
<p>View generated reports and export data.</p>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
Icons With Line
Combines leading icons with the line/underline style variant for an icon-forward, minimal tab strip.
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
export function Pattern() {
return (
<div className="flex w-full max-w-md flex-col gap-6">
<Tabs defaultValue="inbox">
<TabsList className="mb-3.5 w-full" variant="underline">
<TabsTrigger className="gap-2" value="inbox">
Inbox
<Badge size="sm" variant="secondary">
12
</Badge>
</TabsTrigger>
<TabsTrigger className="gap-2" value="drafts">
Drafts
<Badge size="sm" variant="info">
3
</Badge>
</TabsTrigger>
<TabsTrigger className="gap-2" value="sent">
Sent
</TabsTrigger>
<TabsTrigger className="gap-2" value="spam">
Spam
<Badge size="sm" variant="destructive">
24
</Badge>
</TabsTrigger>
</TabsList>
<TabsContent value="inbox">
<Card>
<CardContent>12 unread messages in your inbox.</CardContent>
</Card>
</TabsContent>
<TabsContent value="drafts">
<Card>
<CardContent>3 drafts waiting to be sent.</CardContent>
</Card>
</TabsContent>
<TabsContent value="sent">
<Card>
<CardContent>All sent messages appear here.</CardContent>
</Card>
</TabsContent>
<TabsContent value="spam">
<Card>
<CardContent>24 spam messages detected.</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
Segmented Control
Renders tabs as a segmented control — a pill background slides between options — suited for binary or small option sets.
32,156
Visitors this month
"use client";
import {
CalendarClockIcon,
CalendarIcon,
SquareCheckIcon,
UsersIcon,
} from "lucide-react";
import { useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
export function Pattern() {
const [period, setPeriod] = useState("monthly");
return (
<div className="mx-auto flex w-full max-w-xs flex-col items-center gap-6">
<Tabs onValueChange={setPeriod} value={period}>
<TabsList className="w-full">
<TabsTrigger className="gap-1.5" value="daily">
<CalendarIcon className="size-3.5" />
Daily
</TabsTrigger>
<TabsTrigger className="gap-1.5" value="weekly">
<SquareCheckIcon className="size-4" />
Weekly
</TabsTrigger>
<TabsTrigger className="gap-1.5" value="monthly">
<UsersIcon className="size-4" />
Monthly
</TabsTrigger>
<TabsTrigger className="gap-1.5" value="yearly">
<CalendarClockIcon className="size-3.5" />
Yearly
</TabsTrigger>
</TabsList>
<TabsContent value="daily">
<Card>
<CardContent className="text-center">
<p className="font-bold text-3xl">1,284</p>
<p className="mt-1 text-muted-foreground text-sm">
Visitors today
</p>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="weekly">
<Card>
<CardContent className="text-center">
<p className="font-bold text-3xl">8,942</p>
<p className="mt-1 text-muted-foreground text-sm">
Visitors this week
</p>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="monthly">
<Card>
<CardContent className="text-center">
<p className="font-bold text-3xl">32,156</p>
<p className="mt-1 text-muted-foreground text-sm">
Visitors this month
</p>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="yearly">
<Card>
<CardContent className="text-center">
<p className="font-bold text-3xl">384,721</p>
<p className="mt-1 text-muted-foreground text-sm">
Visitors this year
</p>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
Settings Navigation
A vertical underline tab layout for a settings page with profile, security, notifications, and integrations panels. The active tab animates a left-edge underline indicator.
Profile Settings
Manage your public profile and personal details.
First name
Last name
Bio
import { BellIcon, GlobeIcon, LockIcon, UserIcon } from "lucide-react";
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/ui/tabs";
export function Pattern() {
return (
<div className="mx-auto w-full max-w-lg">
<Tabs className="gap-6" defaultValue="profile" orientation="vertical">
<TabsList className="w-44 shrink-0" variant="underline">
<TabsTab className="justify-start gap-2.5" value="profile">
<UserIcon className="size-4" />
Profile
</TabsTab>
<TabsTab className="justify-start gap-2.5" value="security">
<LockIcon className="size-4" />
Security
</TabsTab>
<TabsTab className="justify-start gap-2.5" value="notifications">
<BellIcon className="size-4" />
Notifications
</TabsTab>
<TabsTab className="justify-start gap-2.5" value="integrations">
<GlobeIcon className="size-4" />
Integrations
</TabsTab>
</TabsList>
<TabsPanel value="profile">
<div className="flex flex-col gap-4">
<div>
<p className="font-semibold text-sm">Profile Settings</p>
<p className="mt-0.5 text-muted-foreground text-xs">
Manage your public profile and personal details.
</p>
</div>
<Separator />
<div className="flex flex-col gap-3">
<div className="grid grid-cols-2 gap-3">
<div>
<p className="mb-1 font-medium text-xs">First name</p>
<div className="rounded-md border border-input bg-muted/40 px-3 py-2 text-sm">
Olivia
</div>
</div>
<div>
<p className="mb-1 font-medium text-xs">Last name</p>
<div className="rounded-md border border-input bg-muted/40 px-3 py-2 text-sm">
Martin
</div>
</div>
</div>
<div>
<p className="mb-1 font-medium text-xs">Email</p>
<div className="rounded-md border border-input bg-muted/40 px-3 py-2 text-muted-foreground text-sm">
olivia@example.com
</div>
</div>
<div>
<p className="mb-1 font-medium text-xs">Bio</p>
<div className="min-h-16 rounded-md border border-input bg-muted/40 px-3 py-2 text-muted-foreground text-sm">
Software engineer & open source contributor.
</div>
</div>
</div>
</div>
</TabsPanel>
<TabsPanel value="security">
<div className="flex flex-col gap-4">
<div>
<p className="font-semibold text-sm">Security</p>
<p className="mt-0.5 text-muted-foreground text-xs">
Manage your password and two-factor authentication.
</p>
</div>
<Separator />
<div className="flex flex-col gap-3">
<div>
<p className="mb-1 font-medium text-xs">Current password</p>
<div className="rounded-md border border-input bg-muted/40 px-3 py-2 text-muted-foreground text-sm">
••••••••
</div>
</div>
<div>
<p className="mb-1 font-medium text-xs">Two-factor auth</p>
<div className="flex items-center justify-between rounded-md border border-input bg-muted/40 px-3 py-2">
<span className="text-muted-foreground text-sm">
Authenticator app
</span>
<span className="font-medium text-emerald-500 text-xs">
Enabled
</span>
</div>
</div>
</div>
</div>
</TabsPanel>
<TabsPanel value="notifications">
<div className="flex flex-col gap-4">
<div>
<p className="font-semibold text-sm">Notification Preferences</p>
<p className="mt-0.5 text-muted-foreground text-xs">
Choose how and when you receive notifications.
</p>
</div>
<Separator />
<div className="flex flex-col gap-2">
{[
"New comments on my posts",
"Mentions and replies",
"Weekly digest email",
"Security alerts",
].map((item) => (
<div
className="flex items-center justify-between rounded-md border border-input px-3 py-2"
key={item}
>
<span className="text-sm">{item}</span>
<div className="h-4 w-8 rounded-full bg-primary" />
</div>
))}
</div>
</div>
</TabsPanel>
<TabsPanel value="integrations">
<div className="flex flex-col gap-4">
<div>
<p className="font-semibold text-sm">Integrations</p>
<p className="mt-0.5 text-muted-foreground text-xs">
Connect third-party tools and services to your account.
</p>
</div>
<Separator />
<div className="flex flex-col gap-2">
{[
{ connected: true, name: "GitHub" },
{ connected: true, name: "Slack" },
{ connected: false, name: "Jira" },
{ connected: false, name: "Figma" },
].map((i) => (
<div
className="flex items-center justify-between rounded-md border border-input px-3 py-2"
key={i.name}
>
<span className="text-sm">{i.name}</span>
<span
className={`text-xs ${i.connected ? "text-emerald-500" : "text-muted-foreground"}`}
>
{i.connected ? "Connected" : "Not connected"}
</span>
</div>
))}
</div>
</div>
</TabsPanel>
</Tabs>
</div>
);
}
Code File Viewer
Per-file tabs styled like an editor tab bar. The active file's content appears below; a copy button in the header writes the current file's code to the clipboard.
import { Button } from "@/components/ui/button"
export default function Page() {
return (
<main className="flex min-h-screen items-center justify-center">
<Button size="lg">Get started</Button>
</main>
)
}"use client";
import { CheckIcon, CopyIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/ui/tabs";
const files: Record<string, string> = {
"index.tsx": `import { Button } from "@/components/ui/button"
export default function Page() {
return (
<main className="flex min-h-screen items-center justify-center">
<Button size="lg">Get started</Button>
</main>
)
}`,
"package.json": `{
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"next": "15.0.0",
"react": "^19.0.0",
"tailwindcss": "^4.0.0"
},
"scripts": {
"dev": "next dev",
"build": "next build"
}
}`,
"styles.css": `@import "tailwindcss";
:root {
--radius: 0.5rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
}`,
};
export function Pattern() {
const [active, setActive] = useState("index.tsx");
const [copied, setCopied] = useState(false);
const copy = () => {
void navigator.clipboard.writeText(files[active] ?? "");
setCopied(true);
setTimeout(() => setCopied(false), 1500);
};
return (
<div className="mx-auto w-full max-w-lg overflow-hidden rounded-xl border border-border">
<Tabs onValueChange={setActive} value={active}>
<div className="flex items-center justify-between border-border border-b bg-muted/30 pt-2 pr-2 pl-2">
<TabsList variant="underline">
{Object.keys(files).map((file) => (
<TabsTab
className="rounded-b-none px-3 text-xs"
key={file}
value={file}
>
{file}
</TabsTab>
))}
</TabsList>
<Button
className="mb-1.5 h-6 gap-1 px-2 text-xs"
onClick={copy}
size="sm"
variant="ghost"
>
{copied ? (
<CheckIcon className="size-3 text-emerald-500" />
) : (
<CopyIcon className="size-3" />
)}
{copied ? "Copied!" : "Copy"}
</Button>
</div>
{Object.entries(files).map(([file, content]) => (
<TabsPanel key={file} value={file}>
<pre className="overflow-x-auto bg-muted/10 p-4 text-foreground/80 text-xs leading-relaxed">
<code>{content}</code>
</pre>
</TabsPanel>
))}
</Tabs>
</div>
);
}
Profile Repositories
Underline-style tabs with numeric badge counts for Repositories and Stars. The Repositories panel lists each repo with language dot, star count, and a Public badge.
Production-ready component library built with Base UI.
Opinionated Next.js starter with auth and Tailwind CSS.
Collection of reusable Tailwind CSS utility classes.
Custom React hooks for common UI patterns.
import { StarIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/ui/tabs";
const repos = [
{
description: "Production-ready component library built with Base UI.",
language: "TypeScript",
name: "ui-cnippet",
stars: 1204,
},
{
description: "Opinionated Next.js starter with auth and Tailwind CSS.",
language: "TypeScript",
name: "next-starter",
stars: 847,
},
{
description: "Collection of reusable Tailwind CSS utility classes.",
language: "CSS",
name: "tailwind-utils",
stars: 432,
},
{
description: "Custom React hooks for common UI patterns.",
language: "TypeScript",
name: "hooks-lab",
stars: 298,
},
];
const langColor: Record<string, string> = {
CSS: "bg-purple-500",
TypeScript: "bg-blue-500",
};
export function Pattern() {
return (
<div className="mx-auto w-full max-w-lg">
<Tabs defaultValue="repositories">
<TabsList variant="underline">
<TabsTab value="overview">Overview</TabsTab>
<TabsTab className="gap-1.5" value="repositories">
Repositories
<Badge size="sm" variant="secondary">
24
</Badge>
</TabsTab>
<TabsTab className="gap-1.5" value="stars">
Stars
<Badge size="sm" variant="secondary">
108
</Badge>
</TabsTab>
<TabsTab value="activity">Activity</TabsTab>
</TabsList>
<TabsPanel className="pt-4" value="overview">
<p className="text-muted-foreground text-sm">
Profile overview content goes here.
</p>
</TabsPanel>
<TabsPanel className="pt-4" value="repositories">
<div className="flex flex-col divide-y divide-border">
{repos.map((repo) => (
<div className="flex flex-col gap-1 py-3" key={repo.name}>
<div className="flex items-center gap-2">
<span className="font-medium text-blue-600 text-sm dark:text-blue-400">
{repo.name}
</span>
<Badge size="sm" variant="outline">
Public
</Badge>
</div>
<p className="text-muted-foreground text-xs">
{repo.description}
</p>
<div className="mt-1 flex items-center gap-3 text-muted-foreground text-xs">
<span className="flex items-center gap-1">
<span
className={`size-2.5 rounded-full ${langColor[repo.language] ?? "bg-muted"}`}
/>
{repo.language}
</span>
<span className="flex items-center gap-1">
<StarIcon className="size-3" />
{repo.stars.toLocaleString()}
</span>
</div>
</div>
))}
</div>
</TabsPanel>
<TabsPanel className="pt-4" value="stars">
<p className="text-muted-foreground text-sm">
Starred repositories appear here.
</p>
</TabsPanel>
<TabsPanel className="pt-4" value="activity">
<p className="text-muted-foreground text-sm">
Recent activity feed goes here.
</p>
</TabsPanel>
</Tabs>
</div>
);
}
Pricing Toggle
A segmented Monthly / Yearly billing toggle with a "Save 20%" badge on the yearly tab. Switching recalculates all plan prices in the card grid below without remounting.
Starter
$9/mo
- 5 projects
- 3 team members
- 5 GB storage
- Email support
Pro
$29/mo
- Unlimited projects
- 20 team members
- 50 GB storage
- Priority support
- Advanced analytics
Enterprise
$99/mo
- Unlimited projects
- Unlimited members
- 1 TB storage
- Dedicated support
- SSO & SAML
- Audit logs
"use client";
import { CheckIcon } from "lucide-react";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/ui/tabs";
const plans = [
{
features: ["5 projects", "3 team members", "5 GB storage", "Email support"],
monthly: 9,
name: "Starter",
yearly: 7,
},
{
features: [
"Unlimited projects",
"20 team members",
"50 GB storage",
"Priority support",
"Advanced analytics",
],
monthly: 29,
name: "Pro",
yearly: 23,
},
{
features: [
"Unlimited projects",
"Unlimited members",
"1 TB storage",
"Dedicated support",
"SSO & SAML",
"Audit logs",
],
monthly: 99,
name: "Enterprise",
yearly: 79,
},
];
export function Pattern() {
const [billing, setBilling] = useState<"monthly" | "yearly">("monthly");
return (
<div className="mx-auto flex w-full max-w-lg flex-col items-center gap-6">
<Tabs
onValueChange={(v) => setBilling(v as "monthly" | "yearly")}
value={billing}
>
<TabsList>
<TabsTab value="monthly">Monthly</TabsTab>
<TabsTab className="gap-1.5" value="yearly">
Yearly
<Badge size="sm" variant="success">
Save 20%
</Badge>
</TabsTab>
</TabsList>
<TabsPanel value="monthly" />
<TabsPanel value="yearly" />
</Tabs>
<div className="grid w-full grid-cols-3 gap-3">
{plans.map((plan) => (
<div
className={`flex flex-col gap-3 rounded-xl border p-4 ${plan.name === "Pro" ? "border-primary ring-1 ring-primary/20" : "border-border"}`}
key={plan.name}
>
<div>
<p className="font-semibold text-sm">{plan.name}</p>
<p className="mt-1 font-bold text-2xl">
${billing === "monthly" ? plan.monthly : plan.yearly}
<span className="font-normal text-muted-foreground text-xs">
/mo
</span>
</p>
</div>
<ul className="flex flex-col gap-1.5">
{plan.features.map((f) => (
<li
className="flex items-start gap-1.5 text-muted-foreground text-xs"
key={f}
>
<CheckIcon className="mt-px size-3 shrink-0 text-emerald-500" />
{f}
</li>
))}
</ul>
<Button
className="mt-auto"
size="sm"
variant={plan.name === "Pro" ? "default" : "outline"}
>
Get started
</Button>
</div>
))}
</div>
</div>
);
}
Dashboard Metrics
Icon tabs for Analytics, Reports, Exports, and Activity. The Analytics panel shows a two-column grid of metric cards; other panels render contextual placeholder content.
Page views
128,430
+12.4% vs last month
Unique visitors
43,210
+8.1% vs last month
Bounce rate
34.2%
-2.3% vs last month
Avg. session
3m 42s
+0.5% vs last month
import {
ActivityIcon,
DownloadIcon,
FileTextIcon,
TrendingUpIcon,
} from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/ui/tabs";
const metrics = [
{
change: "+12.4%",
label: "Page views",
positive: true,
value: "128,430",
},
{
change: "+8.1%",
label: "Unique visitors",
positive: true,
value: "43,210",
},
{ change: "-2.3%", label: "Bounce rate", positive: true, value: "34.2%" },
{
change: "+0.5%",
label: "Avg. session",
positive: true,
value: "3m 42s",
},
];
const reports = [
{ date: "Mar 1, 2025", name: "Q1 Traffic Summary", size: "42 KB" },
{ date: "Feb 1, 2025", name: "Monthly Performance", size: "18 KB" },
{ date: "Jan 15, 2025", name: "Conversion Funnel", size: "31 KB" },
];
export function Pattern() {
return (
<div className="mx-auto w-full max-w-lg">
<Tabs defaultValue="analytics">
<TabsList className="w-full">
<TabsTab className="gap-1.5" value="analytics">
<TrendingUpIcon className="size-3.5" />
Analytics
</TabsTab>
<TabsTab className="gap-1.5" value="reports">
<FileTextIcon className="size-3.5" />
Reports
</TabsTab>
<TabsTab className="gap-1.5" value="exports">
<DownloadIcon className="size-3.5" />
Exports
</TabsTab>
<TabsTab className="gap-1.5" value="activity">
<ActivityIcon className="size-3.5" />
Activity
</TabsTab>
</TabsList>
<TabsPanel className="pt-4" value="analytics">
<div className="grid grid-cols-2 gap-3">
{metrics.map((m) => (
<Card key={m.label}>
<CardContent className="p-4">
<p className="text-muted-foreground text-xs">{m.label}</p>
<p className="mt-1 font-bold text-xl">{m.value}</p>
<p
className={`mt-0.5 text-xs ${m.positive ? "text-emerald-500" : "text-red-500"}`}
>
{m.change} vs last month
</p>
</CardContent>
</Card>
))}
</div>
</TabsPanel>
<TabsPanel className="pt-4" value="reports">
<Card>
<CardContent className="p-0">
<div className="flex flex-col divide-y divide-border">
{reports.map((r) => (
<div
className="flex items-center justify-between px-4 py-3"
key={r.name}
>
<div className="flex flex-col gap-0.5">
<span className="font-medium text-sm">{r.name}</span>
<span className="text-muted-foreground text-xs">
{r.date}
</span>
</div>
<span className="text-muted-foreground text-xs">
{r.size}
</span>
</div>
))}
</div>
</CardContent>
</Card>
</TabsPanel>
<TabsPanel className="pt-4" value="exports">
<Card>
<CardContent>
<p className="text-muted-foreground text-sm">
Export your data in CSV, JSON, or XLSX format. Exports are
available for the last 90 days.
</p>
</CardContent>
</Card>
</TabsPanel>
<TabsPanel className="pt-4" value="activity">
<Card>
<CardContent>
<p className="text-muted-foreground text-sm">
Recent workspace activity and audit events appear here.
</p>
</CardContent>
</Card>
</TabsPanel>
</Tabs>
</div>
);
}

