Skip to content
LuoForge/Tungsten
Components/Tabs·

Tabs

since v0.2.0

Accessible tab container root — manages active tab state and keyboard navigation.

Wraps @radix-ui/react-tabs (per docs/adr/0001-headless-primitive-baseline.md). Radix owns:

  • Keyboard navigation (Arrow keys, Home/End, looping)
  • Roving tabindex + automatic activation on focus
  • ARIA wiring (role="tablist" / tab / tabpanel, aria-selected,

aria-controls, aria-labelledby)

  • Skipping disabled tabs in keyboard navigation

We own the public API (value / defaultValue / onValueChange) and the

visual treatment of the strip and triggers.

Install

Add the package and import the component.

pnpm add @hey-mike/tungsten
pnpm add @hey-mike/tungsten
import { Tabs } from '@hey-mike/tungsten';
import { Tabs } from '@hey-mike/tungsten';

Preview

Same fixtures used by the visual-regression suite.

Usage

apps/docs/app/snapshots/tabs/page.tsx

import { Tabs, TabsList, TabsTrigger, TabsPanel } from '@hey-mike/tungsten';
import { VariantGrid } from '../_components/VariantGrid';
import { HeroSpecimen } from '../_components/HeroSpecimen';

export default function TabsSnapshot() {
  return (
    <VariantGrid
      title="Tabs"
      hero={
        <HeroSpecimen>
          <Tabs defaultValue="results">
            <TabsList>
              <TabsTrigger value="overview">Overview</TabsTrigger>
              <TabsTrigger value="results">Results</TabsTrigger>
            </TabsList>
            <TabsPanel value="overview" className="pt-3">
              <p className="text-ink-2 text-xs">Overview panel.</p>
            </TabsPanel>
            <TabsPanel value="results" className="pt-3">
              <p className="text-ink-1 text-sm font-medium">38 / 42 passed</p>
            </TabsPanel>
          </Tabs>
        </HeroSpecimen>
      }
      variants={[
        {
          label: 'default',
          node: (
            <Tabs defaultValue="results">
              <TabsList>
                <TabsTrigger value="overview">Overview</TabsTrigger>
                <TabsTrigger value="results">Results</TabsTrigger>
                <TabsTrigger value="config">Config</TabsTrigger>
              </TabsList>
              <TabsPanel value="overview" className="pt-4 space-y-2">
                <p className="text-ink-1 text-sm font-medium">Response Quality</p>
                <p className="text-ink-2 text-sm leading-relaxed">
                  Evaluates clarity, depth, and factual correctness of model output across 42 test cases.
                </p>
              </TabsPanel>
              <TabsPanel value="results" className="pt-4 space-y-2">
                <p className="text-ink-3 font-mono text-2xs uppercase tracking-label">passed · 3 min ago</p>
                <p className="text-ink-1 text-sm font-medium">38 / 42 assertions passed</p>
                <p className="text-ink-2 text-sm leading-relaxed">
                  4 cases failed on edge inputs. Review before promoting to production.
                </p>
              </TabsPanel>
              <TabsPanel value="config" className="pt-4 space-y-2">
                <p className="text-ink-3 font-mono text-2xs uppercase tracking-label">model</p>
                <p className="text-ink-1 font-mono text-sm">claude-sonnet-4-6</p>
              </TabsPanel>
            </Tabs>
          ),
        },
        {
          label: 'with disabled',
          node: (
            <Tabs defaultValue="active">
              <TabsList>
                <TabsTrigger value="active">Active</TabsTrigger>
                <TabsTrigger value="disabled" disabled>Disabled</TabsTrigger>
                <TabsTrigger value="other">Other</TabsTrigger>
              </TabsList>
              <TabsPanel value="active" className="pt-4">
                <p className="text-ink-2 text-sm">This tab is active and its panel is visible.</p>
              </TabsPanel>
              <TabsPanel value="other" className="pt-4">
                <p className="text-ink-2 text-sm">Other panel content.</p>
              </TabsPanel>
            </Tabs>
          ),
        },
      ]}
    />
  );
}
import { Tabs, TabsList, TabsTrigger, TabsPanel } from '@hey-mike/tungsten';
import { VariantGrid } from '../_components/VariantGrid';
import { HeroSpecimen } from '../_components/HeroSpecimen';

export default function TabsSnapshot() {
  return (
    <VariantGrid
      title="Tabs"
      hero={
        <HeroSpecimen>
          <Tabs defaultValue="results">
            <TabsList>
              <TabsTrigger value="overview">Overview</TabsTrigger>
              <TabsTrigger value="results">Results</TabsTrigger>
            </TabsList>
            <TabsPanel value="overview" className="pt-3">
              <p className="text-ink-2 text-xs">Overview panel.</p>
            </TabsPanel>
            <TabsPanel value="results" className="pt-3">
              <p className="text-ink-1 text-sm font-medium">38 / 42 passed</p>
            </TabsPanel>
          </Tabs>
        </HeroSpecimen>
      }
      variants={[
        {
          label: 'default',
          node: (
            <Tabs defaultValue="results">
              <TabsList>
                <TabsTrigger value="overview">Overview</TabsTrigger>
                <TabsTrigger value="results">Results</TabsTrigger>
                <TabsTrigger value="config">Config</TabsTrigger>
              </TabsList>
              <TabsPanel value="overview" className="pt-4 space-y-2">
                <p className="text-ink-1 text-sm font-medium">Response Quality</p>
                <p className="text-ink-2 text-sm leading-relaxed">
                  Evaluates clarity, depth, and factual correctness of model output across 42 test cases.
                </p>
              </TabsPanel>
              <TabsPanel value="results" className="pt-4 space-y-2">
                <p className="text-ink-3 font-mono text-2xs uppercase tracking-label">passed · 3 min ago</p>
                <p className="text-ink-1 text-sm font-medium">38 / 42 assertions passed</p>
                <p className="text-ink-2 text-sm leading-relaxed">
                  4 cases failed on edge inputs. Review before promoting to production.
                </p>
              </TabsPanel>
              <TabsPanel value="config" className="pt-4 space-y-2">
                <p className="text-ink-3 font-mono text-2xs uppercase tracking-label">model</p>
                <p className="text-ink-1 font-mono text-sm">claude-sonnet-4-6</p>
              </TabsPanel>
            </Tabs>
          ),
        },
        {
          label: 'with disabled',
          node: (
            <Tabs defaultValue="active">
              <TabsList>
                <TabsTrigger value="active">Active</TabsTrigger>
                <TabsTrigger value="disabled" disabled>Disabled</TabsTrigger>
                <TabsTrigger value="other">Other</TabsTrigger>
              </TabsList>
              <TabsPanel value="active" className="pt-4">
                <p className="text-ink-2 text-sm">This tab is active and its panel is visible.</p>
              </TabsPanel>
              <TabsPanel value="other" className="pt-4">
                <p className="text-ink-2 text-sm">Other panel content.</p>
              </TabsPanel>
            </Tabs>
          ),
        },
      ]}
    />
  );
}

Props

Surface specific to <Tabs />.

PropTypeDefaultDescription
onValueChange((value: string) => void)
orientation"horizontal" | "vertical"Layout axis. Drives arrow-key behaviour (left/right vs. up/down) and the data-orientation attribute on the tablist. @defaultValue 'horizontal'
dir"ltr" | "rtl"Reading direction. 'rtl' reverses horizontal arrow navigation.
activationMode"manual" | "automatic"Whether focusing a tab activates it automatically, or requires Enter/Space. @defaultValue 'automatic'
valuestring
defaultValuestring

Sub-components

Composition slots re-exported from the same module.

TabsList

No library-specific props. Pass through standard HTML attributes for the underlying element.

TabsTrigger

PropTypeDefaultDescription
value*string
iconReactElement<unknown, string | JSXElementConstructor<any>>Optional leading icon. Pass a ReactElement (e.g. a lucide-react icon).

Source