Tabs

Navigation

Horizontal tabbed content switcher.

Preview

Manage your account settings and preferences.

Usage

example.jsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";

export default function Example() {
  return <Tabs />;
}

Source Code

Copy this file into components/ui/tabs.jsx in your project.

tabs.jsx
"use client";

import { forwardRef, createContext, useContext, useState } from "react";
import { cn } from "@/lib/utils";

const TabsContext = createContext({ value: "", onChange: () => {} });

const Tabs = forwardRef(({ className, value: controlledValue, defaultValue = "", onValueChange, children, ...props }, ref) => {
  const [uncontrolled, setUncontrolled] = useState(defaultValue);
  const value = controlledValue !== undefined ? controlledValue : uncontrolled;
  const onChange = (v) => { setUncontrolled(v); onValueChange?.(v); };

  return (
    <TabsContext.Provider value={{ value, onChange }}>
      <div ref={ref} className={cn(className)} {...props}>{children}</div>
    </TabsContext.Provider>
  );
});
Tabs.displayName = "Tabs";

const TabsList = forwardRef(({ className, ...props }, ref) => (
  <div ref={ref} role="tablist" className={cn("inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground", className)} {...props} />
));
TabsList.displayName = "TabsList";

const TabsTrigger = forwardRef(({ className, value, ...props }, ref) => {
  const ctx = useContext(TabsContext);
  return (
    <button ref={ref} role="tab" aria-selected={ctx.value === value} onClick={() => ctx.onChange(value)}
      className={cn("inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
        ctx.value === value ? "bg-background text-foreground shadow-sm" : "hover:bg-background/50 hover:text-foreground",
        className
      )}
      {...props}
    />
  );
});
TabsTrigger.displayName = "TabsTrigger";

const TabsContent = forwardRef(({ className, value, ...props }, ref) => {
  const ctx = useContext(TabsContext);
  if (ctx.value !== value) return null;
  return (
    <div ref={ref} role="tabpanel" className={cn("mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", className)} {...props} />
  );
});
TabsContent.displayName = "TabsContent";

export { Tabs, TabsList, TabsTrigger, TabsContent };

Quick Install

Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.

npm install clsx tailwind-merge