Components

Base Components

Tabs

A set of layered sections (tab panels) that display one panel at a time, with triggers to switch between them.

Example

Make changes to your account here.

Installation

Install the following dependencies:

npm install class-variance-authority

Create a tabs.tsx file and paste the following code into it.

"use client"import * as React from "react"import { cva, type VariantProps } from "class-variance-authority"import { ChevronDown } from "lucide-react"import { Tabs as TabsPrimitive } from "radix-ui"import { cn } from "@/lib/utils"import {  DropdownMenu,  DropdownMenuContent,  DropdownMenuItem,  DropdownMenuTrigger,} from "@/components/ui/dropdown-menu"const TabsContext = React.createContext<{  value?: string  onValueChange?: (value: string) => void}>({})function Tabs({  className,  orientation = "horizontal",  value: controlledValue,  defaultValue,  onValueChange,  ...props}: React.ComponentProps<typeof TabsPrimitive.Root>) {  const [value, setValue] = React.useState(controlledValue || defaultValue)  React.useEffect(() => {    if (controlledValue !== undefined) {      setValue(controlledValue)    }  }, [controlledValue])  const handleValueChange = React.useCallback(    (val: string) => {      if (controlledValue === undefined) {        setValue(val)      }      onValueChange?.(val)    },    [controlledValue, onValueChange]  )  return (    <TabsContext.Provider      value={{ value, onValueChange: handleValueChange }}    >      <TabsPrimitive.Root        data-slot="tabs"        data-orientation={orientation}        orientation={orientation}        value={value}        onValueChange={handleValueChange}        className={cn(          "group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",          className        )}        {...props}      />    </TabsContext.Provider>  )}const tabsListVariants = cva(  "rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",  {    variants: {      variant: {        default: "bg-surface w-fit",        line: "gap-1 bg-transparent w-fit",        overflow: "bg-transparent w-full justify-start",      },    },    defaultVariants: {      variant: "default",    },  })function TabsList({  className,  variant = "default",  children,  ...props}: React.ComponentProps<typeof TabsPrimitive.List> &  VariantProps<typeof tabsListVariants>) {  const { value, onValueChange } = React.useContext(TabsContext)  if (variant === "overflow") {    const tabs = React.Children.map(children, (child) => {      if (        React.isValidElement(child) &&        typeof (child.props as { value?: string }).value === "string"      ) {        return {          value: (child.props as { value: string }).value,          label: (child.props as { children?: React.ReactNode }).children,          disabled: (child.props as { disabled?: boolean }).disabled,        }      }      return null    })?.filter(Boolean)    const activeTab = tabs?.find((tab) => tab?.value === value)    return (      <div className={cn("flex items-center w-full", className)}>        <DropdownMenu>          <DropdownMenuTrigger asChild>            <button              className={cn(                "flex items-center gap-2 px-3 py-1.5 text-sm font-medium border rounded-md bg-background hover:bg-muted/50 transition-colors outline-none",                "focus-visible:ring-2 focus-visible:ring-ring/50"              )}            >              <span className="truncate">{activeTab?.label || "Select tab"}</span>              <ChevronDown className="size-4 opacity-50 shrink-0" />            </button>          </DropdownMenuTrigger>          <DropdownMenuContent align="start" className="w-[200px]">            {tabs?.map((tab) => (              <DropdownMenuItem                key={tab?.value}                disabled={tab?.disabled}                onSelect={() => onValueChange?.(tab?.value)}                className={cn(                  tab?.value === value && "bg-muted font-semibold"                )}              >                {tab?.label}              </DropdownMenuItem>            ))}          </DropdownMenuContent>        </DropdownMenu>      </div>    )  }  return (    <TabsPrimitive.List      data-slot="tabs-list"      data-variant={variant}      className={cn(tabsListVariants({ variant }), className)}      {...props}    >      {children}    </TabsPrimitive.List>  )}const TabsTrigger = function TabsTrigger({  className,  ...props}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {  return (    <TabsPrimitive.Trigger      data-slot="tabs-trigger"      className={cn(        "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",        "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",        "data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground",        "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",        className      )}      {...props}    />  )}TabsTrigger.displayName = "TabsTrigger"function TabsContent({  className,  ...props}: React.ComponentProps<typeof TabsPrimitive.Content>) {  return (    <TabsPrimitive.Content      data-slot="tabs-content"      className={cn("flex-1 outline-none", className)}      {...props}    />  )}export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }

Check the import paths to ensure they match your project setup.

Usage

import {
  Tabs,
  TabsList,
  TabsTrigger,
  TabsContent,
} from "@/components/ui/tabs";
<Tabs defaultValue="tab1" value={value} onValueChange={setValue}>
  <TabsList>
    <TabsTrigger value="tab1">Tab 1</TabsTrigger>
    <TabsTrigger value="tab2">Tab 2</TabsTrigger>
  </TabsList>
  <TabsContent value="tab1">Content for tab 1</TabsContent>
  <TabsContent value="tab2">Content for tab 2</TabsContent>
</Tabs>
Use Tabs as the root with defaultValue (uncontrolled) or value and onValueChange (controlled). Each TabsTrigger and TabsContent must have a matching value. TabsList supports a variant of "default" (pill background) or "line".

Examples

Default

Overview content.

Line variant

Use variant="line" on TabsList for an underline-style tab list.

Content for tab one.

Vertical orientation

Set orientation="vertical" on Tabs to stack the list and content side by side.

General settings.

Disabled trigger

Disable a single tab with the disabled prop on TabsTrigger.

This tab is active.

Overflow variant

The overflow variant transforms the tab list into a dropdown menu. This is useful for compact layouts where a full list of triggers might not fit.

Overview content.

Controlled

Control the active tab with value and onValueChange on Tabs.
const [value, setValue] = React.useState("tab1");
<Tabs value={value} onValueChange={setValue}>
  ...
</Tabs>

API Reference

Tabs

The root component that manages tab state. Supports value, defaultValue, onValueChange, and orientation ("horizontal" | "vertical").
asChildboolean
Default: -
PropTypeDefaultDescription
asChildboolean--

TabsList

Container for the tab triggers. Use the variant prop for "default" (pill) or "line" styling.
asChildboolean
Default: -
variant"line" | "default" | "overflow" | null
Default: default
PropTypeDefaultDescription
asChildboolean--
variant"line" | "default" | "overflow" | nulldefault-

TabsTrigger

The button that switches to a tab. Each trigger must have a unique value that matches a TabsContent panel.
asChildboolean
Default: -
disabledboolean
Default: -
type"button" | "submit" | "reset"
Default: -
PropTypeDefaultDescription
asChildboolean--
disabledboolean--
type"button" | "submit" | "reset"--

TabsContent

The panel shown when its tab is active. Each content must have a value matching a TabsTrigger.
asChildboolean
Default: -
PropTypeDefaultDescription
asChildboolean--

On this page