Skip to main content

Overview

Tabs is a Base UI-powered tab system with Kuzenbo styling and compound subcomponents. It supports horizontal and vertical orientation, className callbacks, and optional animated indicator composition. Use tabs when users should switch between sibling content panels without route changes.

Installation

npm i @kuzenbo/core

Import

import {
  Tabs,
  TabsContent,
  TabsIndicator,
  TabsList,
  TabsTrigger,
} from "@kuzenbo/core/ui/tabs";

Basic Usage

import { Tabs } from "@kuzenbo/core/ui/tabs";

export function TabsBasicExample() {
  return (
    <Tabs defaultValue="overview">
      <Tabs.List variant="default">
        <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
        <Tabs.Trigger value="projects">Projects</Tabs.Trigger>
        <Tabs.Trigger value="account">Account</Tabs.Trigger>
        <Tabs.Indicator />
      </Tabs.List>

      <Tabs.Content value="overview">Overview panel content.</Tabs.Content>
      <Tabs.Content value="projects">Projects panel content.</Tabs.Content>
      <Tabs.Content value="account">Account panel content.</Tabs.Content>
    </Tabs>
  );
}

Full-Width Tabs

import { Tabs } from "@kuzenbo/core/ui/tabs";

export function TabsFullWidthExample() {
  return (
    <Tabs defaultValue="overview">
      <Tabs.List fullWidth variant="line">
        <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
        <Tabs.Trigger value="projects">Projects</Tabs.Trigger>
        <Tabs.Trigger value="account">Account</Tabs.Trigger>
      </Tabs.List>

      <Tabs.Content value="overview">Overview panel content.</Tabs.Content>
      <Tabs.Content value="projects">Projects panel content.</Tabs.Content>
      <Tabs.Content value="account">Account panel content.</Tabs.Content>
    </Tabs>
  );
}

Advanced Usage

import { Tabs } from "@kuzenbo/core/ui/tabs";

export function TabsAdvancedExample() {
  return (
    <Tabs
      className="rounded-md border border-border p-2"
      defaultValue="overview"
    >
      <Tabs.List activateOnFocus variant="line">
        <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
        <Tabs.Trigger value="projects">Projects</Tabs.Trigger>
        <Tabs.Trigger value="account">Account</Tabs.Trigger>
      </Tabs.List>

      <Tabs.Content value="overview">Overview panel content.</Tabs.Content>
      <Tabs.Content value="projects">Projects panel content.</Tabs.Content>
      <Tabs.Content value="account">Account panel content.</Tabs.Content>
    </Tabs>
  );
}

Unified Size Contract

Tabs.List uses shared UISize tokens:
  • xs | sm | md | lg | xl
  • default is md
Family-specific metrics for tab surfaces:
  • List minimum row height per token.
  • Trigger height/padding/text density per token.
  • Indicator radius/density follows the same token.
Precedence rule: Tabs.Trigger/Tabs.Indicator size ?? Tabs.List size ?? "md" Tabs.Trigger and Tabs.Indicator inherit from Tabs.List; size is set at list level.
import { Tabs } from "@kuzenbo/core/ui/tabs";

export function TabsVariantsAndSizes() {
  return (
    <Tabs
      className="rounded-md border border-border p-2"
      defaultValue="overview"
    >
      <Tabs.List size="lg" variant="pill">
        <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
        <Tabs.Trigger value="projects">Projects</Tabs.Trigger>
        <Tabs.Trigger value="account">Account</Tabs.Trigger>
      </Tabs.List>

      <Tabs.Content value="overview">Overview panel content.</Tabs.Content>
      <Tabs.Content value="projects">Projects panel content.</Tabs.Content>
      <Tabs.Content value="account">Account panel content.</Tabs.Content>
    </Tabs>
  );
}

API Reference

Tabs

Root tabs container built on Base UI Tabs.
defaultValue
string
Initial tab value (uncontrolled).
value
string
Current tab value (controlled).
onValueChange
(value: string) => void
Callback when tab changes.
orientation
'horizontal' | 'vertical'
default:"horizontal"
Tab layout direction.

TabsList

Container for tab triggers with variant styles.
variant
'default' | 'line' | 'pill'
default:"default"
Visual style variant.
size
TabsListSize
default:"md"
Size of the tab list: xs | sm | md | lg | xl.
fullWidth
boolean
default:false
Whether triggers should expand to fill available width.
activateOnFocus
boolean
default:false
Whether to activate tabs when focused via keyboard.

TabsTrigger

Interactive tab button.
value
string
required
Unique identifier for this tab.
disabled
boolean
default:false
Whether the tab is disabled.

TabsContent

Panel content for a specific tab.
value
string
required
Tab value this content corresponds to.

TabsIndicator

Animated active tab indicator (mainly for default variant).
className
string
Custom styles for the indicator.

Accessibility

  • Base UI handles ARIA tabs semantics (tablist, tab, tabpanel).
  • Keep trigger labels concise and unique.
  • Disabled triggers should remain visibly disabled and excluded from activation flow.

SSR and RSC

  • Tabs interactions are client-side.
  • TabsIndicator uses renderBeforeHydration to reduce hydration mismatch risk for indicator position.

Styling and Tokens

  • Tabs list/trigger defaults are tokenized (bg-muted, text-muted-foreground, text-foreground, border-border).
  • Keep custom tab surfaces on semantic tokens and avoid raw palette classes.
  • For vertical tabs, use layout classes on root and wrap content in a flexible container.

Troubleshooting

  • Panel does not render: ensure every Tabs.Content value matches an existing trigger value.
  • Indicator not visible in default variant: include <Tabs.Indicator /> inside Tabs.List.
  • Arrow keys move focus but do not activate: set activateOnFocus on Tabs.List.
  • Disabled tab still switching: verify disabled is applied on Tabs.Trigger.

Build docs developers (and LLMs) love