Skip to content

Tabs

In-page tabs that switch panels without a navigation. For sections that are their own URL, use LinkTabs.

LayoutIsland

Examples

Panels

Arrow keys move between tabs. Panels are plain markup: no islands inside.

Run the command below, then restart Claude Code.

Example
<Tabs
  label="Setup instructions"
  items={[
    { name: 'claude-code', label: 'Claude Code', panel: <Text size="sm" tone="ink-2">Run the command below, then restart Claude Code.</Text> },
    { name: 'cursor', label: 'Cursor', panel: <Text size="sm" tone="ink-2">Add the URL under Settings, then MCP, then Add server.</Text> },
    { name: 'codex', label: 'Codex', panel: <Text size="sm" tone="ink-2">Add a [mcp_servers.switchboard] entry to ~/.codex/config.toml.</Text> },
  ]}
/>

Counts, default and disabled

defaultTab picks the open tab; otherwise the first enabled one opens.

8 tools are visible to agents.

Example
<Tabs
  label="Server tools"
  defaultTab="enabled"
  items={[
    { name: 'all', label: 'All', count: 26, panel: <Text size="sm">26 tools on GitHub.</Text> },
    { name: 'enabled', label: 'Enabled', count: 8, panel: <Text size="sm">8 tools are visible to agents.</Text> },
    { name: 'failing', label: 'Failing', count: 0, disabled: true, panel: <Text size="sm">No failing tools.</Text> },
  ]}
/>

Usage

Usage
import { Tabs } from '@switchboard/ui'

<Tabs
  label="Setup instructions"
  items={harnesses.map((h) => ({ name: h.id, label: h.name, panel: <SetupSteps harness={h} /> }))}
/>

Props

Every component also takes mix, for css() and mixins applied after its own style.

Tabs

Tabs props
PropTypeDefaultDescription
label*string–Accessible name of the tab list.
items*TabItem[]–{ name, label, count?, disabled?, panel } per tab.
defaultTabstring–Name of the tab open at first. Defaults to the first enabled tab.

Island: props cross to the browser, so panels must be static markup and css() only (no nested islands, on() or ref()).

TabItem

TabItem props
PropTypeDefaultDescription
name*string–Stable id for the tab.
label*string–Tab text.
countnumber | string–Trailing count.
disabledboolean–Shown but not selectable.
panel*RemixNode–Panel content, serialized as markup.

Accessibility

  • Built on @remix-run/ui/tabs: tablist, tab and tabpanel roles, Left and Right arrows, Home and End, roving focus.
  • The ARIA attributes are in the server HTML, so the markup is correct before hydration.
  • Each panel has tabindex="0" so its content can be reached with Tab even when it has no links.

Guidance

Do

  • Use Tabs for alternatives of the same content, like setup steps per harness.
  • Keep tab labels to one or two words.

Don’t

  • Use Tabs for page sections with their own URL; use LinkTabs.
  • Put islands (copy buttons, menus, switches) inside a panel; they will not hydrate.