Tabs
Tabs switch between related views in the same place and show one panel at a time. The same component is sometimes called Pills.
Installation
npx @smicolon/fasla-ui@latest add tabsRun init once per project before adding components. See Installation.
Tabs is built on Base UI, so it needs the @base-ui/react package. add installs it with the component, and so does the shadcn CLI.
Preview
Account
Make changes to your account here.
Variants
Three styles, set on TabsList so a group never mixes them. boxed, the default, sets the tabs in a muted track and raises the active tab into a pill on it; it suits toolbars and inline view switches. bordered underlines the active tab, for content navigation and dense layouts. lifted raises the active tab out of a baseline like a file folder, for page-level sections.
boxedborderedliftedSizes
Three sizes, also set on TabsList so every tab in a group matches. md is the default. Tabs are 28, 30 or 32px tall, and lg uses 16px text with 20px icons. The boxed track adds 4px around them.
smmdlgStates
The active tab follows value. Hover and focus are real CSS states rather than props, so tab into a group to see the focus ring. A disabled tab is greyed out, and you cannot select it.
You have two orders out for delivery.
You have two orders out for delivery.
You have two orders out for delivery.
Accessibility
- Renders the
tablist,tabandtabpanelroles. Each tab points to its panel witharia-controlswhile that panel is shown, and the panel takes its name from its tab througharia-labelledby. - Tab enters the group on the active tab. The arrow keys move to the previous or next tab and select it, wrapping at the ends. To select with Enter or Space instead, set
activateOnFocustofalse. Home and End jump to the first and last tab. The arrow keys also reach a disabled tab, so a screen reader announces it, but it cannot be selected. - Give
TabsListanaria-labelthat names the group. - An icon before the label is decorative, so mark it
aria-hiddenand let the label name the tab. - Direction is inherited from
dir, never a prop. In right-to-left pages the arrow keys follow the reading order, so the left arrow moves to the next tab.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Active tab, when controlled. |
defaultValue | string | — | Tab active at first. Without it or value, the first enabled tab is active. |
onValueChange | (value, eventDetails) => void | — | Called when the active tab changes. Call eventDetails.cancel() to refuse the change. |
variant | "boxed" | "bordered" | "lifted" | "boxed" | How the active tab is shown. Set on TabsList. |
size | "sm" | "md" | "lg" | "md" | Size of every tab in the group. Set on TabsList. |
activateOnFocus | boolean | true | Whether the arrow keys also select the tab they move to. Set on TabsList. |
TabsTrigger | { value, disabled } | — | A tab button; its value names its panel, and disabled turns it off. |
TabsContent | { value } | — | The panel shown for its value. |
className | string | — | Extra classes for the part. |
Usage
Basic tabs
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
export function AccountTabs() {
return (
<Tabs defaultValue="account">
<TabsList aria-label="Account settings">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">Account content here.</TabsContent>
<TabsContent value="password">Password content here.</TabsContent>
</Tabs>
)
}Another style and size
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
export function BorderedAccountTabs() {
return (
<Tabs defaultValue="account">
<TabsList variant="bordered" size="lg" aria-label="Account settings">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">Account content here.</TabsContent>
<TabsContent value="password">Password content here.</TabsContent>
</Tabs>
)
}With an icon
import { KeyRound, User } from "lucide-react"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
export function AccountTabsWithIcons() {
return (
<Tabs defaultValue="account">
<TabsList aria-label="Account settings">
<TabsTrigger value="account">
<User aria-hidden="true" />
Account
</TabsTrigger>
<TabsTrigger value="password">
<KeyRound aria-hidden="true" />
Password
</TabsTrigger>
</TabsList>
<TabsContent value="account">Account content here.</TabsContent>
<TabsContent value="password">Password content here.</TabsContent>
</Tabs>
)
}Controlled tabs
"use client"
import { useState } from "react"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
export function ControlledAccountTabs() {
const [value, setValue] = useState("account")
return (
<Tabs value={value} onValueChange={setValue}>
<TabsList aria-label="Account settings">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">Account content here.</TabsContent>
<TabsContent value="password">Password content here.</TabsContent>
</Tabs>
)
}Active tab: password