Tabs
Zero-dependency tab list with active underline.
12 REQUESTS QUEUED · 3 IN REVIEW
Source
"use client"
import { Tabs } from "@/registry/warren/tabs/tabs"
export default function TabsDemo() {
return (
<Tabs
items={[
{
id: "intake",
label: "Intake",
content: (
<p className="font-mono text-xs text-muted-foreground">
12 REQUESTS QUEUED · 3 IN REVIEW
</p>
),
},
{
id: "linking",
label: "Linking",
content: (
<p className="font-mono text-xs text-muted-foreground">
5,208 EDGES RESOLVED · 1,003 PENDING
</p>
),
},
{
id: "export",
label: "Export",
content: (
<p className="font-mono text-xs text-muted-foreground">
LAST RUN 14:32:07 UTC · 0 FAILURES
</p>
),
},
]}
/>
)
}
Installation
npx shadcn@latest add https://warren.eduard3v.com/r/tabs.jsonUsage
import { Tabs } from "@/components/ui/tabs"<Tabs
items={[
{ id: "intake", label: "Intake", content: <p>Intake panel</p> },
{ id: "linking", label: "Linking", content: <p>Linking panel</p> },
]}
/>Uncontrolled by default: pass defaultValue to select an initial tab, or leave it out to start on the
first item. Each item takes an id, a label (string or node), and content rendered inside the
active tab panel. role/aria wiring (tablist, tab, tabpanel, aria-selected) is built in,
and label sets the tablist's accessible name (default "tabs").
Keyboard navigation
The tablist implements the WAI-ARIA tabs pattern with automatic activation: ArrowRight/ArrowDown
and ArrowLeft/ArrowUp move focus to and select the next/previous tab (wrapping around), and
Home/End jump to the first/last tab. Only the active tab is in the tab order (tabindex roving
focus), so the whole list is reachable with Tab once plus the arrow keys.
Controlled
LINKING PANEL · SHOWS EDGE RESOLUTION STATE
active: linking
Source
"use client"
import { useState } from "react"
import { Tabs } from "@/registry/warren/tabs/tabs"
export default function TabsControlled() {
const [active, setActive] = useState("linking")
return (
<div className="w-full space-y-2">
<Tabs
value={active}
onValueChange={setActive}
items={[
{
id: "intake",
label: "Intake",
content: (
<p className="font-mono text-xs text-muted-foreground">
INTAKE PANEL · FILTERS BY CREATED_AT
</p>
),
},
{
id: "linking",
label: "Linking",
content: (
<p className="font-mono text-xs text-muted-foreground">
LINKING PANEL · SHOWS EDGE RESOLUTION STATE
</p>
),
},
{
id: "export",
label: "Export",
content: (
<p className="font-mono text-xs text-muted-foreground">
EXPORT PANEL · TARGETS S3 BUCKET
</p>
),
},
]}
/>
<p className="font-mono text-[10px] uppercase tracking-wide text-muted-foreground">
active: {active}
</p>
</div>
)
}
Pass value together with onValueChange to own the active tab. The selected id is the only
argument to onValueChange, so it composes directly with useState or any store.
const [active, setActive] = useState("linking")
<Tabs
value={active}
onValueChange={setActive}
items={[{ id: "linking", label: "Linking", content: <p>Linking panel</p> }]}
/>API Reference
Prop
Type
TabItem
Prop
Type