Tabs
A set of layered sections of content displayed one at a time via tab triggers.
Demo
Content A
Dependencies
clsxtailwind-merge
Props
This is a base primitive. It accepts standard HTML attributes plus className for styling via cn().
Source
"use client";
import { useState, createContext, useContext, useCallback } from "react";
import { cn } from "@/lib/utils";
interface TabsContextValue {
value: string;
onValueChange: (value: string) => void;
}
const TabsContext = createContext<TabsContextValue | null>(null);
export interface TabsProps {
defaultValue: string;
value?: string;
onValueChange?: (value: string) => void;
children?: React.ReactNode;
className?: string;
}
export function Tabs({
defaultValue,
value: controlledValue,
onValueChange,
children,
className,
}: TabsProps) {
const [internalValue, setInternalValue] = useState(defaultValue);
const isControlled = controlledValue !== undefined;
const value = isControlled ? controlledValue : internalValue;
const handleChange = useCallback(
(newValue: string) => {
if (!isControlled) setInternalValue(newValue);
onValueChange?.(newValue);
},
[isControlled, onValueChange],
);
return (
<TabsContext.Provider value={{ value, onValueChange: handleChange }}>
<div className={cn("space-y-4", className)}>{children}</div>
</TabsContext.Provider>
);
}
export function TabsList({
children,
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
role="tablist"
className={cn(
"inline-flex h-10 items-center rounded-lg border border-gray-200 bg-gray-100 p-1",
className,
)}
{...props}
>
{children}
</div>
);
}
export function TabsTrigger({
value,
children,
className,
...props
}: React.ButtonHTMLAttributes<HTMLButtonElement> & { value: string }) {
const ctx = useContext(TabsContext);
const isActive = ctx?.value === value;
return (
<button
role="tab"
aria-selected={isActive}
onClick={() => ctx?.onValueChange(value)}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium transition-all",
isActive
? "bg-white text-gray-900 shadow-sm"
: "text-gray-500 hover:text-gray-700",
className,
)}
{...props}
>
{children}
</button>
);
}
export function TabsContent({
value,
children,
className,
...props
}: React.HTMLAttributes<HTMLDivElement> & { value: string }) {
const ctx = useContext(TabsContext);
if (ctx?.value !== value) return null;
return (
<div role="tabpanel" className={cn("mt-2", className)} {...props}>
{children}
</div>
);
}
CLI
npx genui add tabs