Tabs System
Five distinct navigation patterns for specifically defined problems. From minimal inline switching to dense configuration sidebars.
Live Preview
Account
Update your profile information. This panel is exclusively for Account configuration. Notice how the layout adapts based on the variant selection.
Recommended Use
- Use Inline for major page sections (e.g. Profile, Settings).
- Use Vertical for dense configuration panels with 5+ categories.
- Use Segmented for high-frequency toggles (e.g. Monthly/Yearly).
Avoid Using When
- Do not use Inline Tabs for navigation between distinct pages. Use a Sidebar.
- Do not nest tabs inside tabs unless absolutely necessary (Segmented inside Inline is okay).
Installation
components/ui/tabs.tsx
1"use client";2import React, { useState } from "react";3import { cn } from "@/lib/utils";4import { motion } from "framer-motion";56type TabVariant = "inline" | "segmented" | "scrollable" | "vertical" | "pill";78const TabsContext = React.createContext<{9 activeTab: string;10 setActiveTab: (id: string) => void;11 variant: TabVariant;12} | undefined>(undefined);1314export const Tabs = ({15 defaultValue,16 variant = "inline",17 children,18 className19}: any) => {20 const [activeTab, setActiveTab] = useState(defaultValue);21 return (22 <TabsContext.Provider value={{ activeTab, setActiveTab, variant }}>23 <div className={cn("w-full", variant === "vertical" ? "flex gap-8" : "flex flex-col", className)}>24 {children}25 </div>26 </TabsContext.Provider>27 );28};2930export const TabsList = ({ children, className }: any) => {31 const { variant } = React.useContext(TabsContext)!;32 return (33 <div className={cn(34 "flex overflow-x-auto no-scrollbar",35 variant === "inline" && "border-b border-white/5 gap-6",36 variant === "scrollable" && "border-b border-white/5 gap-6 overflow-x-auto pb-0.5",37 variant === "segmented" && "bg-white/5 p-1 rounded-lg gap-1 inline-flex self-start",38 variant === "pill" && "gap-2",39 variant === "vertical" && "flex-col w-48 shrink-0 border-r border-white/5 gap-1 pr-4",40 className41 )}>42 {children}43 </div>44 );45};4647export const TabTrigger = ({ value, children }: any) => {48 const { activeTab, setActiveTab, variant } = React.useContext(TabsContext)!;49 const isActive = activeTab === value;5051 return (52 <button53 onClick={() => setActiveTab(value)}54 className={cn(55 "relative text-sm font-medium transition-colors outline-none whitespace-nowrap",56 (variant === "inline" || variant === "scrollable") && cn(57 "pb-3 pt-2 text-neutral-400 hover:text-white",58 isActive ? "text-white" : ""59 ),60 variant === "segmented" && cn(61 "px-3 py-1.5 rounded-md text-neutral-400 z-10",62 isActive ? "text-white shadow-sm" : "hover:text-white hover:bg-white/5"63 ),64 variant === "pill" && cn(65 "px-4 py-2 rounded-full border border-transparent",66 isActive ? "bg-white/10 border-white/10 text-white" : "text-neutral-400 hover:text-white hover:bg-white/5"67 ),68 variant === "vertical" && cn(69 "px-4 py-2.5 rounded-md text-left w-full text-neutral-400",70 isActive ? "bg-white/5 text-white" : "hover:bg-white/5 hover:text-white"71 )72 )}73 >74 {isActive && (variant === "inline" || variant === "scrollable") && (75 <motion.div layoutId="active-tab" className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-500" />76 )}77 {isActive && variant === "segmented" && (78 <motion.div layoutId="active-tab" className="absolute inset-0 bg-neutral-700/80 rounded-md -z-10" />79 )}80 {children}81 </button>82 );83};8485export const TabsContent = ({ value, children }: any) => {86 const { activeTab } = React.useContext(TabsContext)!;87 if (activeTab !== value) return null;88 return <div className="animate-in fade-in slide-in-from-bottom-2 duration-200">{children}</div>;89};