Tabs
Tabbed content panels built on Base UI
Installation
bunx @prototyperco/cli add tabspnpm dlx shadcn@latest add https://prototyper-ui.com/r/tabs.jsonnpx shadcn@latest add https://prototyper-ui.com/r/tabs.jsonyarn dlx shadcn@latest add https://prototyper-ui.com/r/tabs.jsonbunx --bun shadcn@latest add https://prototyper-ui.com/r/tabs.jsonThis will add the following files to your project:
components/ui/tabs.tsx
Usage
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
<Tabs defaultValue="tab-1">
<TabsList>
<TabsTrigger value="tab-1">Tab 1</TabsTrigger>
<TabsTrigger value="tab-2">Tab 2</TabsTrigger>
</TabsList>
<TabsContent value="tab-1">Content 1</TabsContent>
<TabsContent value="tab-2">Content 2</TabsContent>
</Tabs>;Anatomy
<Tabs>
<TabsList>
<TabsTrigger />
<TabsTrigger />
</TabsList>
<TabsContent />
<TabsContent />
</Tabs>| Sub-component | data-slot | Purpose | Required |
|---|---|---|---|
Tabs | tabs | Root provider, manages active tab state | Yes |
TabsList | tabs-list | Container for tab triggers | Yes |
TabsTrigger | tabs-trigger | Button that activates a tab panel | Yes |
TabsContent | tabs-content | Content panel associated with a trigger | Yes |
Examples
Disabled
Disabled Dynamic
Disabled Items
Dynamic
Focus
Vertical
Styling
Data Slots
Use data-slot attributes to target specific parts of the tabs:
| Slot name | Element |
|---|---|
tabs | Root wrapper |
tabs-list | Container for tab triggers |
tabs-trigger | Individual tab button |
tabs-content | Tab panel content area |
Customization Examples
/* Make the tab list full-width */
[data-slot="tabs-list"] {
@apply w-full;
}
/* Custom active tab style */
[data-slot="tabs-trigger"][data-active] {
@apply bg-primary text-primary-foreground;
}{
/* Override list variant via className */
}
<TabsList className="bg-transparent gap-2">
<TabsTrigger value="tab-1">Tab 1</TabsTrigger>
<TabsTrigger value="tab-2">Tab 2</TabsTrigger>
</TabsList>;API Reference
Tabs
Root component that manages active tab state and context.
Prop
Type
All Base UI Tabs.Root props are forwarded via ...props.
TabsList
Container for tab triggers with variant styling.
Prop
Type
All Base UI Tabs.List props are forwarded via ...props.
TabsTrigger
Button that activates a tab panel when clicked.
Prop
Type
All Base UI Tabs.Tab props are forwarded via ...props.
TabsContent
Content panel that is shown when its associated trigger is active.
Prop
Type
All Base UI Tabs.Panel props are forwarded via ...props.
tabsListVariants
A cva helper exported for use outside of the <TabsList> component (e.g., applying tab list styles to custom elements).
import { tabsListVariants } from "@/components/ui/tabs";
<div className={tabsListVariants({ variant: "line" })}>Custom tab list</div>;Accessibility
Keyboard Interactions
| Key | Action |
|---|---|
ArrowRight | Moves focus to the next tab trigger (horizontal orientation) |
ArrowLeft | Moves focus to the previous tab trigger (horizontal orientation) |
ArrowDown | Moves focus to the next tab trigger (vertical orientation) |
ArrowUp | Moves focus to the previous tab trigger (vertical orientation) |
Home | Moves focus to the first tab trigger |
End | Moves focus to the last tab trigger |
Space | Activates the focused tab trigger |
Enter | Activates the focused tab trigger |
Tab | Moves focus into the active tab panel, then to the next element |
ARIA Attributes
- The tab list renders with
role="tablist". - Each trigger renders with
role="tab"andaria-selectedindicating whether it is active. - Each content panel renders with
role="tabpanel". aria-controlson each trigger links to its corresponding panel.aria-labelledbyon each panel links back to its corresponding trigger.aria-orientationis set on the tab list based on theorientationprop.aria-disabledis set on disabled tab triggers.
Compose
This component is available in @prototyperco/compose.
Catalog Definition
import { z } from "zod";
import { defineComponent } from "@prototyperco/compose/catalog";
export default defineComponent({
description: "A tabbed interface for switching between content panels",
props: z.object({
tabs: z
.array(
z.object({
label: z.string().describe("Tab button label"),
value: z.string().describe("Tab identifier value"),
}),
)
.describe("Tab definitions"),
value: z.string().optional().describe("Active tab value (bindable)"),
variant: z
.enum(["default", "line"])
.optional()
.describe("Visual style variant"),
}),
events: ["change"],
example: {
tabs: [
{ label: "Account", value: "account" },
{ label: "Settings", value: "settings" },
{ label: "Billing", value: "billing" },
],
value: "account",
},
});Example Spec
{
"root": "tabsContainer",
"elements": {
"tabsContainer": {
"type": "Tabs",
"props": {
"tabs": [
{ "label": "Account", "value": "account" },
{ "label": "Settings", "value": "settings" },
{ "label": "Billing", "value": "billing" }
],
"value": "account"
},
"children": ["accountContent", "settingsContent", "billingContent"],
"$bindState": { "path": "/activeTab", "event": "change" }
},
"accountContent": {
"type": "Text",
"props": { "content": "Manage your account details here." }
},
"settingsContent": {
"type": "Text",
"props": { "content": "Configure your preferences." }
},
"billingContent": {
"type": "Text",
"props": { "content": "View and manage your billing." }
}
}
}Learn more in the Compose documentation.