gridland
Components

Tabs

A compound tab component with triggers and content panels

A compound tab component. Declare triggers and content panels side by side — the active panel renders automatically.

TabBar
?

Run demo

Terminal
bunx @gridland/demo tabs
Terminal
curl -fsSL https://raw.githubusercontent.com/thoughtfulllc/gridland/main/scripts/run-demo.sh | bash -s tabs

Installation

Terminal
bunx create-gridland add tab-bar

Usage

import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tab-bar"
<Tabs defaultValue="files">
  <TabsList focusId="tabs" autoFocus>
    <TabsTrigger value="files">Files</TabsTrigger>
    <TabsTrigger value="search">Search</TabsTrigger>
    <TabsTrigger value="git">Git</TabsTrigger>
  </TabsList>
  <TabsContent value="files">
    <text>Browse project files</text>
  </TabsContent>
  <TabsContent value="search">
    <text>Search across files</text>
  </TabsContent>
  <TabsContent value="git">
    <text>View git status</text>
  </TabsContent>
</Tabs>

TabsList is focused-is-interactive: pass focusId and arrow/h/l keys fire whenever that id is focused (no separate "Enter to select" step needed). Wrap your app in GridlandProvider or a FocusProvider for the focus system to route keys.

Examples

Full Example

Tabs – with content
?

Simple API

For cases where you don't need content panels, use the TabBar convenience wrapper.

Simple API
import { TabBar } from "@/components/ui/tab-bar"

<TabBar
  focusId="tabs"
  autoFocus
  label="View"
  options={["Files", "Search", "Git"]}
  selectedIndex={0}
  onValueChange={setSelectedIndex}
/>

With Label

Show a text label before the triggers.

With label
<Tabs defaultValue="code">
  <TabsList label="Language">
    <TabsTrigger value="code">Code</TabsTrigger>
    <TabsTrigger value="preview">Preview</TabsTrigger>
  </TabsList>
  <TabsContent value="code">
    <text>Source code</text>
  </TabsContent>
  <TabsContent value="preview">
    <text>Live preview</text>
  </TabsContent>
</Tabs>

Controlled

Use value and onValueChange to control the active tab externally.

Controlled
const [tab, setTab] = useState("files")

<Tabs value={tab} onValueChange={setTab}>
  <TabsList>
    <TabsTrigger value="files">Files</TabsTrigger>
    <TabsTrigger value="search">Search</TabsTrigger>
  </TabsList>
  <TabsContent value="files">
    <text>Files panel</text>
  </TabsContent>
  <TabsContent value="search">
    <text>Search panel</text>
  </TabsContent>
</Tabs>

Unfocused

Set focused={false} on TabsList to render in a dimmed, unfocused style.

Unfocused
<Tabs defaultValue="a">
  <TabsList focused={false}>
    <TabsTrigger value="a">Tab A</TabsTrigger>
    <TabsTrigger value="b">Tab B</TabsTrigger>
  </TabsList>
</Tabs>

Disabled Tab

Disable individual tabs to prevent keyboard navigation to them.

Disabled tab
<Tabs defaultValue="code">
  <TabsList>
    <TabsTrigger value="code">Code</TabsTrigger>
    <TabsTrigger value="preview" disabled>Preview</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="code">
    <text>Source code</text>
  </TabsContent>
  <TabsContent value="settings">
    <text>Settings panel</text>
  </TabsContent>
</Tabs>

No Separator

Hide the horizontal separator line below the tab bar.

No separator
<Tabs defaultValue="a">
  <TabsList separator={false}>
    <TabsTrigger value="a">Tab A</TabsTrigger>
    <TabsTrigger value="b">Tab B</TabsTrigger>
  </TabsList>
</Tabs>

Compound Components

ComponentDescription
TabsRoot container with active tab state
TabsListHorizontal tab bar built from TabsTrigger children
TabsTriggerDeclares a tab option. Does not render on its own — TabsList reads its props.
TabsContentRenders its children only when its value matches the active tab
TabBarSimple convenience wrapper (no content panels)

API Reference

Tabs

PropTypeDefaultDescription
valuestring-Controlled active tab value
defaultValuestring""Default active tab (uncontrolled)
onValueChange(value: string) => void-Called when the active tab changes
childrenReactNode-Sub-components

TabsList

PropTypeDefaultDescription
labelstring-Text label shown before the triggers
focusedbooleantrueWhether the tab bar appears focused
activeColorstringtheme.accentForeground color of the active trigger
separatorbooleantrueShow horizontal separator below triggers
focusIdstringauto-generatedStable id for the focus system. Arrow/h/l navigation fires while this id is focused.
autoFocusbooleanfalseFocus this tab bar on mount
childrenReactNode-TabsTrigger components

TabsTrigger

PropTypeDefaultDescription
valuestring-Unique value linking this trigger to its TabsContent
disabledbooleanfalseDisables the tab — skipped during keyboard navigation and rendered dimmed
childrenReactNode-Label text displayed in the tab bar

TabsContent

PropTypeDescription
valuestringMust match the active tab value to render
childrenReactNodeContent shown when this tab is active

TabBar

PropTypeDefaultDescription
labelstring-Text label shown before the options
optionsstring[]-Array of option strings to display
selectedIndexnumber-Zero-based index of the selected option
focusedbooleantrueWhether the tab bar appears focused
activeColorstringtheme.accentForeground color of the selected option
separatorbooleantrueShow horizontal separator below tabs
focusIdstringauto-generatedStable id for the focus system
autoFocusbooleanfalseFocus this tab bar on mount
onValueChange(index: number) => void-Called when the active tab changes via keyboard navigation

Controls

KeyAction
/ hPrevious tab (wraps around)
/ lNext tab (wraps around)