Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/sub-tabs.jsonInstalls to @/components/ui/sub-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 SubTabs({ className, ...props }: TabsPrimitive.Root.Props) {
return <TabsPrimitive.Root data-slot="sub-tabs" className={cn('flex flex-col gap-2', className)} {...props} />
}
function SubTabsList({ className, ...props }: TabsPrimitive.List.Props) {
return (
<TabsPrimitive.List
data-slot="sub-tabs-list"
className={cn(
'inline-flex h-10 w-fit items-center gap-2 rounded-lg border border-border bg-muted p-[3px] text-foreground',
className,
)}
{...props}
/>
)
}
function SubTabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
return (
<TabsPrimitive.Tab
data-slot="sub-tabs-trigger"
className={cn(
"inline-flex h-full items-center justify-center gap-1.5 rounded-md border border-transparent px-2 text-sm font-normal whitespace-nowrap text-muted-foreground transition-all outline-none cursor-pointer hover:bg-background/50 hover:text-primary focus-visible:border-ring 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:bg-background data-active:font-semibold data-active:text-primary data-active:shadow-sm data-active:hover:bg-secondary [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
)
}
function SubTabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
return (
<TabsPrimitive.Panel
data-slot="sub-tabs-content"
className={cn('flex-1 text-sm outline-none focus-visible:ring-3 focus-visible:ring-ring', className)}
{...props}
/>
)
}
export { SubTabs, SubTabsList, SubTabsTrigger, SubTabsContent }A secondary, lower-emphasis tab bar for switching views inside a page section, typically nested below the
main Tabs. Built on the Base UI Tabs primitive (from the @base-ui/react package). The list is an outlined h-8
bg-muted bar and the active trigger gets a bg-background fill with text-primary.
Features
- Built on the Base UI Tabs primitive
- Same sizing and focus ring as the other controls (h-8, rounded-lg,
ring-3 ring-ring/50) - Keyboard navigation and screen reader support
- Controlled and uncontrolled modes
- Icons in triggers (sized to
size-4automatically) - Disabled triggers
Usage
import { SubTabs, SubTabsContent, SubTabsList, SubTabsTrigger } from '@/components/ui/sub-tabs'
export function SubTabsDemo() {
return (
<SubTabs defaultValue="incoming" className="w-full max-w-md">
<SubTabsList>
<SubTabsTrigger value="incoming">Incoming</SubTabsTrigger>
<SubTabsTrigger value="outgoing">Outgoing</SubTabsTrigger>
</SubTabsList>
<SubTabsContent value="incoming">Incoming documents…</SubTabsContent>
<SubTabsContent value="outgoing">Outgoing documents…</SubTabsContent>
</SubTabs>
)
}Examples
Default
The document section of a building permit case, split into incoming, outgoing and internal notes.
With Icons
Icons placed before the label in each trigger, inside a resident's "My pages" card.
Nested in Tabs
The typical placement: the main Tabs switch between sections of a case, and SubTabs switch views inside one of them.
Controlled
Controlled via value / onValueChange, so other parts of the screen can read and change the active view.
Disabled State
A trigger with disabled is dimmed and can't be selected – here the decision stays locked until the assessment is done.
Full Width
Give the list w-full and each trigger flex-1 to stretch the bar, for example for opening hours.
Anatomy
<SubTabs>
<SubTabsList>
<SubTabsTrigger />
</SubTabsList>
<SubTabsContent />
</SubTabs>SubTabs- root (a flex column withgap-2), accepts all Base UI Tabs Root propsSubTabsList- the outlined trigger barSubTabsTrigger- a single tab; active state usesdata-activeSubTabsContent- the panel for a tab
Accessibility
Follows the WAI-ARIA tabs pattern:
- Proper roles (
tablist,tab,tabpanel) Arrow keysmove between tabs,Home/Endjump to the first / last tabTabmoves focus into the active panel- Visible focus ring on triggers and panels
Customization
<SubTabs defaultValue="tab1">
<SubTabsList className="w-full">
<SubTabsTrigger value="tab1" className="flex-1">
<Settings2Icon />
Tab 1
</SubTabsTrigger>
</SubTabsList>
<SubTabsContent value="tab1" className="rounded-lg border p-4">
Content 1
</SubTabsContent>
</SubTabs>Related Components
- Tabs - For the primary tab bar
- Toggle Group - For switching a setting rather than a view