Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/tabs.jsonInstalls to @/components/ui/tabs.
Manual installation
Install the dependencies:
pnpm add @base-ui/react@^1.8.0Copy the source into your project:
'use client'
import { Tabs as TabsPrimitive } from '@base-ui/react/tabs'
import { cn } from '@/lib/utils'
function Tabs({ className, orientation = 'horizontal', ...props }: TabsPrimitive.Root.Props) {
return (
<TabsPrimitive.Root
data-slot="tabs"
data-orientation={orientation}
orientation={orientation}
className={cn('group/tabs flex gap-2 data-horizontal:flex-col', className)}
{...props}
/>
)
}
function TabsList({ className, ...props }: TabsPrimitive.List.Props) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
'inline-flex w-fit items-center group-data-vertical/tabs:flex-col group-data-vertical/tabs:items-stretch',
className,
)}
{...props}
/>
)
}
function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
return (
<TabsPrimitive.Tab
data-slot="tabs-trigger"
className={cn(
// Rapid underline tabs with shadcn sizing: a muted line under every tab, a thicker primary line on the
// active tab, and primary text on hover and when active (solid text-primary for 4.5:1 contrast)
"relative inline-flex h-10 cursor-pointer items-center justify-center gap-1.5 rounded-md px-3 text-sm font-medium whitespace-nowrap text-foreground transition-colors outline-none hover:text-primary focus-visible:ring-3 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:font-semibold data-active:text-primary group-data-vertical/tabs:justify-start has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
'after:absolute after:rounded-none after:bg-muted after:transition-colors after:duration-200 hover:after:bg-primary/90 data-active:after:bg-primary data-active:hover:after:bg-primary/90',
'group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-0 group-data-horizontal/tabs:after:h-0.5 group-data-horizontal/tabs:hover:after:h-[3px] group-data-horizontal/tabs:data-active:after:h-[3px]',
'group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:left-0 group-data-vertical/tabs:after:w-0.5',
className,
)}
{...props}
/>
)
}
function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
return (
<TabsPrimitive.Panel
data-slot="tabs-content"
className={cn('flex-1 rounded-md text-sm outline-none focus-visible:ring-3 focus-visible:ring-ring', className)}
{...props}
/>
)
}
export { Tabs, TabsList, TabsTrigger, TabsContent }A tabbed interface component built on top of Base UI Tabs, providing an accessible way to organize and switch between different views.
Features
- Underline style: a thin muted line under every tab, and a thicker primary line with semibold primary text on the active tab; hovered tabs turn primary
- Horizontal and vertical orientation (
orientation="vertical"onTabs) - Icons in triggers, sized automatically; mark them with
data-icon="inline-start"ordata-icon="inline-end"for tighter padding - Controlled (
value/onValueChange) and uncontrolled (defaultValue) modes - Disabled triggers
- Keyboard navigation and screen reader support
Usage
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
export function TabsDemo() {
return (
<Tabs defaultValue="overview" className="w-full max-w-md">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="history">History</TabsTrigger>
</TabsList>
<TabsContent value="overview">
Case details…
</TabsContent>
<TabsContent value="history">
Events in the case…
</TabsContent>
</Tabs>
)
}Examples
Default
A case view split into overview, applicant and history, each in a card.
Vertical
Set orientation="vertical" on Tabs to stack the triggers beside the content; the line moves to the left edge.
Here as the settings menu on a resident's My pages.
With Icons
Icons with data-icon="inline-start" in the triggers, and a count badge on the messages tab of a guardian's school view.
Icon Only
Triggers with only an icon. Give each trigger an aria-label, and wrap it in a Tooltip so sighted users see the name too.
Controlled
Controlled with value / onValueChange, so Back and Next buttons can move through the steps of an application.
Disabled State
A disabled trigger for a step that isn't available yet – appeals open once a decision has been made.
Props
Tabs
defaultValue/value/onValueChange- Uncontrolled or controlled active tab;onValueChange(value, eventDetails)orientation-'horizontal' | 'vertical'(default'horizontal')
TabsList
activateOnFocus- Activate a tab as soon as it receives focus with the arrow keys (defaultfalse: arrow keys move focus,Enter/Spaceactivate)loopFocus- Whether arrow keys wrap from the last tab to the first (defaulttrue)- All Base UI
Tabs.Listprops
TabsTrigger
value- The value of the tabdisabled- Whether the tab is disabled
TabsContent
value- The value of the tab this panel belongs tokeepMounted- Keep the panel in the DOM while it is hidden
Anatomy
<Tabs>
<TabsList>
<TabsTrigger />
</TabsList>
<TabsContent />
</Tabs>Accessibility
The Tabs component follows WAI-ARIA guidelines:
- Uses proper ARIA roles (
tablist,tab,tabpanel) - Keyboard navigation:
Tab: Move focus to the next focusable elementArrow keys: Navigate between tabsSpace/Enter: Activate tabHome/End: Go to first/last tab
- Focus management within panels
- Screen reader announcements
- Clear visual feedback
- Proper labeling
Best Practices
-
Content Organization:
- Group related content
- Use clear labels
- Keep content focused
- Consider tab order
-
Visual Design:
- Maintain clear hierarchy
- Use consistent spacing
- Provide visual feedback
- Consider mobile layout
-
Interaction:
- Support keyboard navigation
- Handle focus management
- Provide clear feedback
- Consider loading states
-
Accessibility:
- Use descriptive labels
- Maintain focus order
- Support screen readers
- Consider keyboard users
-
Implementation:
- Handle state changes
- Manage content updates
- Consider performance
- Test edge cases
Customization
Custom Styling
Modify the appearance using Tailwind classes:
<Tabs className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger
value="tab1"
className="data-active:bg-primary data-active:text-primary-foreground"
>
Tab 1
</TabsTrigger>
</TabsList>
<TabsContent
value="tab1"
className="rounded-lg border p-4"
>
Content 1
</TabsContent>
</Tabs>Vertical Orientation
Create vertically oriented tabs:
<Tabs defaultValue="account" orientation="vertical">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">
Account settings...
</TabsContent>
<TabsContent value="password">
Password settings...
</TabsContent>
</Tabs>With Icons
Add icons to tab triggers:
<TabsTrigger value="settings">
<Settings2Icon data-icon="inline-start" />
Settings
</TabsTrigger>Related Components
- Accordion - For vertically stacked sections
- Sub Tabs - For secondary tab navigation
- Card - For content containers
- Navigation Menu - For site navigation