React tab components built with shadcn/ui and Tailwind CSS. Five patterns covering horizontal, vertical, icon, scrollable and pill-style tabs — all with controlled or uncontrolled state options.
Key Classes Reference
| Component | Purpose |
|---|---|
Tabs | Root — manages active tab state |
TabsList | Container for trigger buttons |
TabsTrigger | Individual tab button |
TabsContent | Panel rendered when tab is active |
defaultValue | Uncontrolled initial active tab |
value + onValueChange | Controlled tab state |
1. Basic Tabs
Standard horizontal tabs with shadcn/ui Tabs component.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
export default function BasicTabs() {
return (
<div className="p-6">
<Tabs defaultValue="overview">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
<TabsContent value="overview">
<Card>
<CardHeader>
<CardTitle>Overview</CardTitle>
<CardDescription>Your dashboard summary for this month.</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-3 gap-4">
{[["Revenue", "$12,400"], ["Users", "1,293"], ["Orders", "847"]].map(([label, value]) => (
<div key={label} className="rounded-xl bg-gray-50 p-4">
<p className="text-xs text-muted-foreground">{label}</p>
<p className="text-2xl font-bold mt-1">{value}</p>
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="analytics">
<Card>
<CardHeader>
<CardTitle>Analytics</CardTitle>
<CardDescription>Detailed traffic and conversion data.</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">Analytics content goes here.</p>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="settings">
<Card>
<CardHeader>
<CardTitle>Settings</CardTitle>
<CardDescription>Manage your account preferences.</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">Settings content goes here.</p>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
)
}2. Icon Tabs
Tabs with icons and badge counts using lucide-react.
import { useState } from "react"
import { LayoutDashboard, Users, FileText, Bell } from "lucide-react"
import { Badge } from "@/components/ui/badge"
const tabs = [
{ id: "dashboard", label: "Dashboard", icon: LayoutDashboard, count: null },
{ id: "users", label: "Users", icon: Users, count: 24 },
{ id: "docs", label: "Docs", icon: FileText, count: null },
{ id: "alerts", label: "Alerts", icon: Bell, count: 3 },
]
export default function IconTabs() {
const [active, setActive] = useState("dashboard")
return (
<div className="p-6">
<div className="flex border-b border-gray-200 gap-1">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActive(tab.id)}
className={`flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 transition-colors ${
active === tab.id
? "border-red-500 text-red-500"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
}`}
>
<tab.icon className="h-4 w-4" />
{tab.label}
{tab.count && (
<Badge className="h-4 px-1.5 text-xs bg-red-500 text-white">{tab.count}</Badge>
)}
</button>
))}
</div>
<div className="p-4 text-sm text-muted-foreground">
Active tab: <span className="font-semibold text-gray-900">{active}</span>
</div>
</div>
)
}3. Vertical Tabs
Sidebar-style vertical tab layout for settings pages.
import { useState } from "react"
import { User, Lock, Bell, CreditCard, HelpCircle } from "lucide-react"
const tabs = [
{ id: "profile", label: "Profile", icon: User },
{ id: "security", label: "Security", icon: Lock },
{ id: "notifications", label: "Notifications", icon: Bell },
{ id: "billing", label: "Billing", icon: CreditCard },
{ id: "support", label: "Support", icon: HelpCircle },
]
const content: Record<string, { title: string; desc: string }> = {
profile: { title: "Profile Settings", desc: "Update your name, avatar and public profile information." },
security: { title: "Security", desc: "Manage your password, two-factor authentication and sessions." },
notifications: { title: "Notifications", desc: "Choose what you want to be notified about." },
billing: { title: "Billing & Plans", desc: "Manage your subscription and payment methods." },
support: { title: "Help & Support", desc: "Get help from our team or browse documentation." },
}
export default function VerticalTabs() {
const [active, setActive] = useState("profile")
return (
<div className="p-6">
<div className="flex gap-6">
<nav className="w-44 shrink-0 space-y-1">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActive(tab.id)}
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors text-left ${
active === tab.id
? "bg-red-50 text-red-600"
: "text-gray-600 hover:bg-gray-100 hover:text-gray-900"
}`}
>
<tab.icon className="h-4 w-4 shrink-0" />
{tab.label}
</button>
))}
</nav>
<div className="flex-1 rounded-xl border border-gray-200 p-6">
<h3 className="font-bold text-lg mb-1">{content[active].title}</h3>
<p className="text-sm text-muted-foreground">{content[active].desc}</p>
</div>
</div>
</div>
)
}4. Scrollable Tabs
Horizontally scrollable tab bar for many tab items.
import { useState } from "react"
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
const data: Record<string, { revenue: string; orders: number }> = {
Jan: { revenue: "$8,200", orders: 312 }, Feb: { revenue: "$9,100", orders: 341 },
Mar: { revenue: "$11,400", orders: 420 }, Apr: { revenue: "$10,200", orders: 389 },
May: { revenue: "$12,800", orders: 467 }, Jun: { revenue: "$13,400", orders: 512 },
Jul: { revenue: "$14,200", orders: 543 }, Aug: { revenue: "$13,800", orders: 521 },
Sep: { revenue: "$12,100", orders: 462 }, Oct: { revenue: "$11,600", orders: 441 },
Nov: { revenue: "$15,200", orders: 584 }, Dec: { revenue: "$18,400", orders: 702 },
}
export default function ScrollableTabs() {
const [active, setActive] = useState("Jun")
return (
<div className="p-6">
<div className="overflow-x-auto">
<div className="flex border-b border-gray-200 min-w-max">
{months.map((m) => (
<button
key={m}
onClick={() => setActive(m)}
className={`px-4 py-2 text-sm font-medium border-b-2 whitespace-nowrap transition-colors ${
active === m
? "border-red-500 text-red-500"
: "border-transparent text-gray-500 hover:text-gray-700"
}`}
>
{m}
</button>
))}
</div>
</div>
<div className="mt-6 grid grid-cols-2 gap-4">
<div className="rounded-xl bg-red-50 p-5">
<p className="text-xs text-red-400 font-medium uppercase tracking-wide">Revenue</p>
<p className="text-3xl font-bold text-red-600 mt-1">{data[active].revenue}</p>
</div>
<div className="rounded-xl bg-gray-50 p-5">
<p className="text-xs text-gray-400 font-medium uppercase tracking-wide">Orders</p>
<p className="text-3xl font-bold text-gray-900 mt-1">{data[active].orders}</p>
</div>
</div>
</div>
)
}5. Pill Tabs
Pill-shaped tab buttons with background highlight instead of underline.
import { useState } from "react"
const tabs = [
{ id: "all", label: "All Templates", count: 28 },
{ id: "angular", label: "Angular", count: 12 },
{ id: "react", label: "React", count: 8 },
{ id: "bootstrap", label: "Bootstrap", count: 8 },
]
const templates = [
{ name: "Marvel Dashboard", framework: "angular" },
{ name: "PORTO", framework: "bootstrap" },
{ name: "Kiosk", framework: "react" },
{ name: "Proctu", framework: "react" },
{ name: "ORYO", framework: "angular" },
{ name: "Consult", framework: "bootstrap" },
]
export default function PillTabs() {
const [active, setActive] = useState("all")
const filtered = active === "all"
? templates
: templates.filter((t) => t.framework === active)
return (
<div className="p-6">
<div className="flex flex-wrap gap-2 mb-6 bg-gray-100 p-1 rounded-xl w-fit">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActive(tab.id)}
className={`flex items-center gap-1.5 px-4 py-1.5 rounded-lg text-sm font-medium transition-all ${
active === tab.id
? "bg-white text-red-500 shadow-sm"
: "text-gray-500 hover:text-gray-700"
}`}
>
{tab.label}
<span className={`text-xs px-1.5 py-0.5 rounded-full ${
active === tab.id ? "bg-red-50 text-red-500" : "bg-gray-200 text-gray-500"
}`}>
{tab.id === "all" ? filtered.length : templates.filter(t => t.framework === tab.id).length}
</span>
</button>
))}
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{filtered.map((t) => (
<div key={t.name} className="rounded-xl border border-gray-200 p-4">
<p className="font-semibold text-sm">{t.name}</p>
<p className="text-xs text-muted-foreground capitalize mt-0.5">{t.framework}</p>
</div>
))}
</div>
</div>
)
}Frequently Asked Questions
Need a Full React + Next.js Dashboard Template?
Get a complete React + Next.js dashboard with 50+ components — built by the same team behind BootstrapPlanet.
Browse Templates →Use code FIRST30 for 30% off.