Skip to content

Tabs

Terminal-styled tabbed interface with horizontal and vertical orientation support.

Preview

Installation

$pnpm exec dgadd add ui/tabs
[Installs to]src/components/ui/tabs[Item]ui/tabs

dgadd is not public on npm yet. Until the first release, pack @diffgazer/add from the repository and install that tarball into this app, which is what puts dgadd on pnpm exec.

UI components require Tailwind CSS v4. Local copy mode imports src/styles/styles.css; package mode uses @diffgazer/ui CSS once packages are available.

Usage

tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";export default function TabsDefault() {  return (    <Tabs defaultValue="preview">      <TabsList>        <TabsTrigger value="preview">Preview</TabsTrigger>        <TabsTrigger value="code">Code</TabsTrigger>        <TabsTrigger value="tests">Tests</TabsTrigger>      </TabsList>      <TabsContent value="preview">        <div className="border border-border p-4 text-sm">          Live preview of the component renders here.        </div>      </TabsContent>      <TabsContent value="code">        <div className="border border-border p-4 text-sm font-mono text-muted-foreground">          Source code displayed here with syntax highlighting.        </div>      </TabsContent>      <TabsContent value="tests">        <div className="border border-border p-4 text-sm text-success">All 12 tests passing.</div>      </TabsContent>    </Tabs>  );}

Examples

Trigger Variants

Preview

Vertical Orientation

Preview

Controlled with Disabled Tab

Preview

Keyboard Navigation

Preview

Wrapped Reflow

Preview

API Reference

Tabs

NameTypeDefaultDescription
valuestringControlled active tab value. Pair with onChange.
defaultValuestringInitial active tab value for uncontrolled mode. Defaults to the first enabled Trigger.
onChange(value: string) => voidFired when the active tab changes.
orientation"horizontal" | "vertical""horizontal"Tab list axis. Switches arrow-key navigation direction and aria-orientation.
variant"default" | "bracket" | "pill" | "underline""underline"Visual style applied to triggers and the list.
size"sm" | "md""sm"Size variant / trigger density token. "sm" is 36px, rising to a 44px target on coarse pointers; "md" is always a 44px target.
activationMode"automatic" | "manual""automatic"Automatic activates on focus; manual requires Enter or Space.
childrenrequiredReactNodeTabs.List and Tabs.Content subparts.

Tabs.List

NameTypeDefaultDescription
wrapbooleantrueAllows horizontal triggers and label text to wrap within the available width. Vertical lists ignore this option.
loopbooleantrueWhen true, arrow navigation wraps from last to first trigger and vice versa.
onNavigationBoundaryReached(direction: "previous" | "next", event: KeyboardEvent, key: string) => voidFired when arrow navigation reaches the first/last trigger with loop disabled.
childrenReactNodeTabs.Trigger children.

Tabs.Trigger

NameTypeDefaultDescription
valuerequiredstringStable identifier matched against Tabs value and the paired Tabs.Content.
disabledbooleanfalseDisables activation and removes the trigger from arrow navigation.
childrenReactNodeTrigger label.

Tabs.Content

NameTypeDefaultDescription
valuerequiredstringStable identifier paired with the matching Tabs.Trigger.
childrenReactNodePanel content. Hidden when its trigger is not active.

Data attributes

AttributeApplies toValuesDescription
data-stateTabs.Trigger / Tabs.Content"active" | "inactive"Active tab/panel state for styling and visibility.
data-orientationTabs.List"horizontal" | "vertical"Keyboard/navigation axis.
data-valueTabs.Trigger / Tabs.Contenttab valueStable value pairing triggers and panels.
data-variantTabs.List / Tabs.Trigger"default" | "bracket" | "pill" | "underline"Visual variant propagated to list and triggers.
data-wrapTabs.List / Tabs.Trigger"true" | "false"Whether the horizontal list uses wrapped, row-local active treatments.

Accessibility

Keyboard Navigation

TabsList has built-in keyboard navigation via @diffgazer/keys. ArrowLeft/ArrowRight (horizontal) or ArrowUp/ArrowDown (vertical) navigate between tabs. Home/End jump to first/last tab. Enter/Space activate the focused tab. With loop disabled, arrow navigation stops at the edges and fires onNavigationBoundaryReached for composite focus handoff.

KeyAction
ArrowLeft / ArrowRightMoves focus across horizontal tab triggers.
ArrowUp / ArrowDownMoves focus across vertical tab triggers.
Home / EndMoves focus to the first or last enabled trigger.
Enter / SpaceActivates the focused tab in manual mode.

Keyboard Navigation

Preview

Notes

Requires @diffgazer/keys (package mode)

TabsList's arrow-key navigation imports from the required @diffgazer/keys peer. Package consumers must install @diffgazer/keys with @diffgazer/ui; packages are not yet published to npm, so until the first release install both from locally packed tarballs. Importing @diffgazer/ui/components/tabs without keys fails at module load with an error naming the missing @diffgazer/keys package. Copy/dgadd consumers do not need the package — copy mode rewrites the keyboard hooks to local source.

Composition Contract

Use Tabs.List, Tabs.Trigger, and Tabs.Content as explicit children in the Tabs JSX tree. Custom tab visuals belong inside Tabs.Trigger. Components that create triggers internally from an opaque wrapper are not part of the current public contract.

Orientation Support

Tabs default to horizontal orientation. Set orientation='vertical' to stack triggers vertically. Keyboard arrow directions automatically align with the orientation.

Horizontal reflow

Horizontal Tabs.List wraps triggers and label text by default so constrained layouts do not create page-level horizontal scrolling. Pill and underline variants use a selected treatment on each trigger when rows wrap. Set wrap={false} only when a single-row layout is guaranteed.

Panel ownership

Tabs.Content owns its DOM id and hidden state so the matching trigger's aria-controls reference and inactive-panel visibility stay synchronized. Pair triggers and panels with value instead of overriding id or hidden.

Tab list semantics

Tabs.List owns its tablist role, orientation, variant, and wrapping state. Native attributes can label or describe the list but cannot replace those compound-component invariants.

Source

Install via CLI: pnpm exec dgadd add ui/tabs. Keyboard hooks are included as standalone copies. For the full experience, use --integration keys.

Highlighted source loads after this disclosure opens. Browse the source repository.