- Accordion
- Agent Thinking
- Alert
- Alert Dialog
- Announcement
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Checkbox
- Checkbox Card
- Code Block
- Collapsible
- Color Picker
- Combobox
- Command
- Composer
- Composer Loader
- Context Menu
- Data Grid
- Data Table
- Date Picker
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- File Upload
- Hover Card
- Input
- Input Group
- Input Otp
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Meter
- Native Select
- Navigation Menu
- Number Field
- Pagination
- Period Switcher
- Popover
- Progress
- Questionnaire
- Radio Card
- Radio Group
- Resizable
- Reveal
- Scroll Area
- Search Field
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Social Button
- Sonner
- Spinner
- Status Dot
- Switch
- Switch Card
- Table
- Tabs
- Tag Group
- Textarea
- Theme Toggle
- Time Field
- Toggle
- Toggle Group
- Tooltip
- Trace
- Tree View
- Agent Creator 01
- Agent Limits Card 01
- Agent Progress 01
- AI Chat 01
- AI Image Generation 01
- AI Profile 01
- Alerts Card 01
- Auth Card 01
- Calendar 01
- Composer 01
- Composer Attachments 01
- Composer Panel 01
- Finance Dashboard 01
- Home Dashboard 01
- Marketing Dashboard 01
- Medical Profile 01
- Meeting Scheduler 01
- Multi Agent Chat 01
- Notification Center 01
- Patient Info Card 01
- Project Board 01
- Questionnaire 01
- Settings Modal 01
- Sidebar 01
- Stat Cards 01
- Task List 01
- Web Search 01
- Activity Rings Card 01
- Area Chart Card 01
- Bar List Card 01
- Combo Chart Card 01
- Contributions Card 01
- Earnings Chart Card 01
- Funnel Chart Card 01
- Heatmap Chart Card 01
- Line Chart Card 01
- Most Active Days Card 01
- Orders Chart Card 01
- Radar Chart Card 01
- Radial Chart Card 01
- Revenue Chart Card 01
- Sankey Chart Card 01
- Scatter Chart Card 01
- Sleep Score Card 01
- Stage Bars Card 01
- Steps Card 01
Your banners appear here.
Today’s schedule.
Installation
$ pnpm dlx shadcn@latest add @cobalt/tabs
Options
<TabsList variant="line">underline tabs;<TabsTrigger count>adds a trailing count.<TabsList variant="pill">fully rounded blue pill that slides between triggers;variant="pill-muted"is the quieter 10px neutral version.<TabsList variant="pill-soft">text tabs with a fully rounded, primary-tinted active tab.<TabsList variant="ghost">plain text tabs; the active tab turns darker and medium weight.<TabsList variant="segmented">default tabs on the toggle-group track with wider triggers.<Tabs size="flush">drops the gap between the list and the content.<TabsList indicator>slides one active surface between triggers on the default variant.
Source
"use client";
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
import { cva } from "class-variance-authority";
import type { VariantProps } from "class-variance-authority";
import { cn } from "cn";
function Tabs({
className,
orientation = "horizontal",
size = "default",
...props
}: TabsPrimitive.Root.Props & { size?: "default" | "flush" }) {
return (
<TabsPrimitive.Root
data-slot="tabs"
orientation={orientation}
className={cn(
"group/tabs flex gap-4 data-horizontal:flex-col",
size === "flush" && "gap-0",
className
)}
{...props}
/>
);
}
const tabsListVariants = cva(
"group/tabs-list inline-flex w-fit items-center justify-center rounded-[8px] p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
{
defaultVariants: {
variant: "default",
},
variants: {
variant: {
default: "bg-muted",
pill: "relative gap-1 bg-transparent p-0 group-data-horizontal/tabs:h-auto",
"pill-muted":
"relative gap-1 bg-transparent p-0 group-data-horizontal/tabs:h-auto",
ghost: "gap-0.5 bg-transparent p-0 group-data-horizontal/tabs:h-auto",
"pill-soft":
"gap-1 bg-transparent p-0 group-data-horizontal/tabs:h-auto",
segmented: "bg-[var(--toggle-group-track,var(--muted))]",
line: "relative w-full justify-start gap-1 border-b border-[var(--surface-strong,var(--border))] bg-transparent p-0 group-data-horizontal/tabs:h-auto group-data-vertical/tabs:w-fit group-data-vertical/tabs:items-stretch group-data-vertical/tabs:border-r group-data-vertical/tabs:border-b-0",
},
},
}
);
const pillTriggerClassName =
"group-data-[variant=pill]/tabs-list:h-auto group-data-[variant=pill]/tabs-list:flex-none group-data-[variant=pill]/tabs-list:cursor-pointer group-data-[variant=pill]/tabs-list:gap-1 group-data-[variant=pill]/tabs-list:rounded-full group-data-[variant=pill]/tabs-list:border-0 group-data-[variant=pill]/tabs-list:px-2 group-data-[variant=pill]/tabs-list:py-[5px] group-data-[variant=pill]/tabs-list:text-muted-foreground! group-data-[variant=pill]/tabs-list:shadow-none group-data-[variant=pill]/tabs-list:hover:bg-[var(--surface-hover,var(--muted))] group-data-[variant=pill]/tabs-list:data-active:bg-transparent! group-data-[variant=pill]/tabs-list:data-active:text-[var(--tabs-pill-selected-foreground,var(--primary))]! group-data-[variant=pill]/tabs-list:focus-visible:border-0 group-data-[variant=pill]/tabs-list:focus-visible:ring-2 group-data-[variant=pill]/tabs-list:focus-visible:ring-ring group-data-[variant=pill]/tabs-list:focus-visible:outline-none dark:group-data-[variant=pill]/tabs-list:data-active:border-0 " +
"group-data-[variant=pill-muted]/tabs-list:h-auto group-data-[variant=pill-muted]/tabs-list:flex-none group-data-[variant=pill-muted]/tabs-list:cursor-pointer group-data-[variant=pill-muted]/tabs-list:gap-1 group-data-[variant=pill-muted]/tabs-list:rounded-[10px] group-data-[variant=pill-muted]/tabs-list:border-0 group-data-[variant=pill-muted]/tabs-list:px-2 group-data-[variant=pill-muted]/tabs-list:py-[5px] group-data-[variant=pill-muted]/tabs-list:text-muted-foreground! group-data-[variant=pill-muted]/tabs-list:shadow-none group-data-[variant=pill-muted]/tabs-list:hover:bg-[var(--surface-hover,var(--muted))] group-data-[variant=pill-muted]/tabs-list:data-active:bg-transparent! group-data-[variant=pill-muted]/tabs-list:data-active:text-foreground! group-data-[variant=pill-muted]/tabs-list:focus-visible:border-0 group-data-[variant=pill-muted]/tabs-list:focus-visible:ring-2 group-data-[variant=pill-muted]/tabs-list:focus-visible:ring-ring group-data-[variant=pill-muted]/tabs-list:focus-visible:outline-none dark:group-data-[variant=pill-muted]/tabs-list:data-active:border-0";
// The sliding indicator (variant "default") can be restyled from the list:
// set --tabs-indicator-background: and --tabs-indicator-radius: on TabsList.
function TabsList({
className,
variant = "default",
indicator = false,
children,
...props
}: TabsPrimitive.List.Props &
VariantProps<typeof tabsListVariants> & { indicator?: boolean }) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
data-variant={variant}
data-indicator={indicator && variant === "default" ? "" : undefined}
className={cn(
tabsListVariants({ variant }),
indicator &&
variant === "segmented" &&
"relative **:data-[slot=tabs-trigger]:before:hidden",
"data-indicator:relative data-indicator:**:data-[slot=tabs-trigger]:before:hidden",
indicator &&
variant === "pill-soft" &&
"relative **:data-[slot=tabs-trigger]:before:hidden",
className
)}
{...props}
>
{indicator && variant === "pill-soft" && (
<TabsPrimitive.Indicator className="pointer-events-none absolute top-0 left-0 h-[var(--active-tab-height)] w-[var(--active-tab-width)] translate-x-[var(--active-tab-left)] translate-y-[var(--active-tab-top)] rounded-full bg-[var(--tabs-pill-selected-background,color-mix(in_srgb,var(--primary)_15%,transparent))] transition-[translate,width,height] duration-300 ease-[cubic-bezier(0.34,1.2,0.64,1)] motion-reduce:transition-none" />
)}
{indicator && variant === "default" && (
<TabsPrimitive.Indicator className="pointer-events-none absolute top-0 left-0 h-[var(--active-tab-height)] w-[var(--active-tab-width)] translate-x-[var(--active-tab-left)] translate-y-[var(--active-tab-top)] rounded-[var(--tabs-indicator-radius,6px)] bg-[var(--tabs-indicator-background,var(--background))] shadow-[0_1px_0_0_rgb(0_0_0/0.05)] transition-[translate,width,height] duration-200 ease-[ease] motion-reduce:transition-none dark:bg-[var(--tabs-indicator-background,color-mix(in_oklab,var(--input)_30%,transparent))]" />
)}
{indicator && variant === "segmented" && (
<TabsPrimitive.Indicator className="pointer-events-none absolute top-0 left-0 h-[var(--active-tab-height)] w-[var(--active-tab-width)] translate-x-[var(--active-tab-left)] translate-y-[var(--active-tab-top)] rounded-[6px] bg-background shadow-sm transition-[translate,width,height] duration-200 ease-[ease] motion-reduce:transition-none dark:bg-[var(--surface-card,var(--input))]" />
)}
{(variant === "pill" || variant === "pill-muted") && (
<TabsPrimitive.Indicator
className={cn(
"pointer-events-none absolute top-0 left-0 h-[var(--active-tab-height)] w-[var(--active-tab-width)] translate-x-[var(--active-tab-left)] translate-y-[var(--active-tab-top)] transition-[translate,width,height] duration-300 ease-[cubic-bezier(0.34,1.2,0.64,1)] motion-reduce:transition-none",
variant === "pill"
? "rounded-full bg-[var(--tabs-pill-selected-background,color-mix(in_srgb,var(--primary)_10%,transparent))]"
: "rounded-[10px] bg-[var(--tabs-pill-muted-background,var(--muted))]"
)}
/>
)}
{children}
{variant === "line" && (
<TabsPrimitive.Indicator className="pointer-events-none absolute bottom-0 left-0 h-0.5 w-[var(--active-tab-width)] translate-x-[var(--active-tab-left)] bg-primary transition-[translate,width] duration-200 ease-[ease] motion-reduce:transition-none group-data-vertical/tabs:top-0 group-data-vertical/tabs:right-0 group-data-vertical/tabs:bottom-auto group-data-vertical/tabs:left-auto group-data-vertical/tabs:h-[var(--active-tab-height)] group-data-vertical/tabs:w-0.5 group-data-vertical/tabs:translate-x-0 group-data-vertical/tabs:translate-y-[var(--active-tab-top)]" />
)}
</TabsPrimitive.List>
);
}
function TabsTrigger({
className,
count,
children,
...props
}: TabsPrimitive.Tab.Props & { count?: React.ReactNode }) {
return (
<TabsPrimitive.Tab
data-slot="tabs-trigger"
className={cn(
"relative isolate inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-[6px] border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-[background-color,border-color,box-shadow] data-active:transition-none group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=line]/tabs-list:data-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-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"data-active:text-foreground dark:data-active:text-foreground",
"before:pointer-events-none before:absolute before:-inset-px before:-z-10 before:rounded-[inherit] before:opacity-0 data-active:before:opacity-100 group-data-[variant=default]/tabs-list:before:bg-background group-data-[variant=default]/tabs-list:before:shadow-sm dark:group-data-[variant=default]/tabs-list:before:bg-input/30",
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
pillTriggerClassName,
"group-data-[variant=ghost]/tabs-list:h-auto group-data-[variant=ghost]/tabs-list:flex-none group-data-[variant=ghost]/tabs-list:cursor-pointer group-data-[variant=ghost]/tabs-list:rounded-[6px] group-data-[variant=ghost]/tabs-list:px-2.5 group-data-[variant=ghost]/tabs-list:py-1 group-data-[variant=ghost]/tabs-list:font-normal group-data-[variant=ghost]/tabs-list:text-muted-foreground! group-data-[variant=ghost]/tabs-list:data-active:bg-transparent! group-data-[variant=ghost]/tabs-list:data-active:font-medium group-data-[variant=ghost]/tabs-list:data-active:text-foreground! group-data-[variant=ghost]/tabs-list:shadow-none group-data-[variant=ghost]/tabs-list:not-data-active:hover:text-foreground! dark:group-data-[variant=ghost]/tabs-list:data-active:border-transparent",
"group-data-[variant=pill-soft]/tabs-list:h-auto group-data-[variant=pill-soft]/tabs-list:flex-none group-data-[variant=pill-soft]/tabs-list:cursor-pointer group-data-[variant=pill-soft]/tabs-list:gap-2 group-data-[variant=pill-soft]/tabs-list:rounded-full group-data-[variant=pill-soft]/tabs-list:px-2.5 group-data-[variant=pill-soft]/tabs-list:py-1 group-data-[variant=pill-soft]/tabs-list:font-normal group-data-[variant=pill-soft]/tabs-list:text-muted-foreground! group-data-[variant=pill-soft]/tabs-list:before:bg-[var(--tabs-pill-selected-background,color-mix(in_srgb,var(--primary)_15%,transparent))] group-data-[variant=pill-soft]/tabs-list:data-active:font-medium group-data-[variant=pill-soft]/tabs-list:data-active:text-primary! group-data-[variant=pill-soft]/tabs-list:shadow-none group-data-[variant=pill-soft]/tabs-list:not-data-active:hover:text-foreground! dark:group-data-[variant=pill-soft]/tabs-list:data-active:border-transparent",
"group-data-[variant=segmented]/tabs-list:px-3 group-data-[variant=segmented]/tabs-list:text-muted-foreground/75 group-data-[variant=segmented]/tabs-list:hover:text-foreground group-data-[variant=segmented]/tabs-list:data-active:text-foreground dark:group-data-[variant=segmented]/tabs-list:data-active:border-transparent dark:group-data-[variant=segmented]/tabs-list:data-active:focus-visible:border-transparent group-data-[variant=segmented]/tabs-list:before:bg-background group-data-[variant=segmented]/tabs-list:before:shadow-sm dark:group-data-[variant=segmented]/tabs-list:before:bg-[var(--surface-card,var(--input))]",
"group-data-[variant=line]/tabs-list:h-auto group-data-[variant=line]/tabs-list:flex-none group-data-[variant=line]/tabs-list:cursor-pointer group-data-[variant=line]/tabs-list:gap-2.5 group-data-[variant=line]/tabs-list:rounded-none group-data-[variant=line]/tabs-list:border-0 group-data-[variant=line]/tabs-list:px-2.5 group-data-[variant=line]/tabs-list:py-2 group-data-[variant=line]/tabs-list:font-normal group-data-[variant=line]/tabs-list:text-foreground group-data-[variant=line]/tabs-list:shadow-none group-data-[variant=line]/tabs-list:after:hidden group-data-[variant=line]/tabs-list:data-active:font-medium group-data-[variant=line]/tabs-list:data-active:text-primary dark:group-data-[variant=line]/tabs-list:text-foreground dark:group-data-[variant=line]/tabs-list:data-active:text-primary group-data-[variant=line]/tabs-list:data-disabled:cursor-not-allowed group-data-[variant=line]/tabs-list:focus-visible:rounded-[4px] group-data-[variant=line]/tabs-list:focus-visible:border-transparent group-data-[variant=line]/tabs-list:focus-visible:ring-2 group-data-[variant=line]/tabs-list:focus-visible:ring-ring group-data-[variant=line]/tabs-list:focus-visible:outline-none data-disabled:cursor-not-allowed data-disabled:opacity-50 motion-reduce:transition-none",
className
)}
{...props}
>
{count != null ? (
<span className="inline-flex items-center gap-1.5">{children}</span>
) : (
children
)}
{count != null && (
<span className="inline-flex items-center justify-center rounded-[4px] bg-[var(--tabs-count-background,color-mix(in_srgb,var(--foreground)_10%,transparent))] px-1 py-px text-xs leading-4 font-medium tracking-[0.15px] text-foreground opacity-50 [[data-active]>&]:bg-[var(--tabs-count-selected-background,color-mix(in_srgb,var(--primary)_15%,transparent))] [[data-active]>&]:text-primary [[data-active]>&]:opacity-100 group-data-[variant=segmented]/tabs-list:min-w-5 group-data-[variant=segmented]/tabs-list:bg-[var(--badge-neutral-background,var(--muted))] group-data-[variant=segmented]/tabs-list:text-muted-foreground group-data-[variant=segmented]/tabs-list:opacity-100 group-data-[variant=segmented]/tabs-list:[[data-active]>&]:bg-[var(--badge-neutral-background,var(--muted))] group-data-[variant=segmented]/tabs-list:[[data-active]>&]:text-muted-foreground">
{count}
</span>
)}
</TabsPrimitive.Tab>
);
}
function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
return (
<TabsPrimitive.Panel
data-slot="tabs-content"
className={cn("flex-1 text-sm outline-none", className)}
{...props}
/>
);
}
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }; On this page