Tabs
A track cut on the slant at both ends, and a slab of the accent that springs to the active tab. Every edge leans the same way, so at either end the slab sits parallel to the track.
Install
# once per project: where @zzzui items come frompnpm dlx shadcn@latest registry add @zzzui=https://zzzui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zzzui/tabsExamples
With panels
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/zero/tabs";export default function TabsBasic() { return ( <Tabs defaultValue="today" className="w-full max-w-md"> <TabsList> <TabsTab value="today">Today</TabsTab> <TabsTab value="week">This week</TabsTab> <TabsTab value="later">Later</TabsTab> </TabsList> <TabsPanel value="today" className="font-medium text-zero-text-2"> Three errands, one delivery across the river, a call at nine. </TabsPanel> <TabsPanel value="week" className="font-medium text-zero-text-2"> The workshop reopens on Thursday; the parts arrive the day before. </TabsPanel> <TabsPanel value="later" className="font-medium text-zero-text-2"> Nothing planned yet. </TabsPanel> </Tabs> );}Sizes
import { Tabs, TabsList, TabsTab } from "@/components/zero/tabs";export default function TabsSizes() { return ( <div className="grid justify-items-center gap-5"> {(["sm", "md", "lg"] as const).map((size) => ( <Tabs key={size} size={size} defaultValue="notes"> <TabsList> <TabsTab value="notes">Notes</TabsTab> <TabsTab value="level">Level</TabsTab> <TabsTab value="schedule">Schedule</TabsTab> </TabsList> </Tabs> ))} </div> );}Icons, long labels, a disabled tab
import { LockIcon, SearchIcon } from "@/components/zero/icons";import { Tabs, TabsList, TabsTab } from "@/components/zero/tabs";export default function TabsIcons() { return ( <Tabs defaultValue="all"> <TabsList> <TabsTab value="all">All</TabsTab> <TabsTab value="open">Open commissions</TabsTab> <TabsTab value="search"> <SearchIcon /> Find </TabsTab> <TabsTab value="locked" disabled> <LockIcon /> Locked </TabsTab> </TabsList> </Tabs> );}Hotkeys, as a game's shoulder buttons
import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/zero/tabs";export default function TabsHotkeys() { return ( <Tabs defaultValue="board" className="w-full max-w-lg"> <TabsList hotkeys={["q", "e"]}> <TabsTab value="board">Board</TabsTab> <TabsTab value="today">Today</TabsTab> <TabsTab value="rank">Rank</TabsTab> </TabsList> <TabsPanel value="board" className="font-medium text-zero-text-2"> Press Q or E anywhere on the page to step between tabs. </TabsPanel> <TabsPanel value="today" className="font-medium text-zero-text-2"> Four shifts today; the night one is yours. </TabsPanel> <TabsPanel value="rank" className="font-medium text-zero-text-2"> Level 8, sixty points from 9. </TabsPanel> </Tabs> );}Behaviour
The slab springs to the new tab and settles in about a quarter of a second; under reduced motion it lands without travelling. While it slides, the part of each label over it turns dark and the rest stays light, so no label is ever dark on the dark track.
Arrow keys move focus along the tabs; Enter or Space activates the focused one, which then fills itself straight away inside the selection frame. Tabs are horizontal only. With hotkeys, two keys step back and forth from anywhere on the page, the way a game pages with its shoulder buttons.
Props
Tabs
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | A tab is 32, 40 or 52 px tall; the track adds its outline and inset. |
Also takes every prop of Base UI's Tabs.Root.
TabsList
| Prop | Type | Default | Description |
|---|---|---|---|
| hotkeys | [string, string] | — | Two keys that step to the previous and next tab from anywhere outside a text field; shown at the track's ends. |
Also takes every prop of Base UI's Tabs.List.
TabsTab
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | Which panel this tab shows. |
Also takes every prop of Base UI's Tabs.Tab.
TabsPanel
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | The tab that shows this panel. |
Also takes every prop of Base UI's Tabs.Panel.