Back to Design System

Tabs

Horizontal navigation for switching between related content panes without leaving the current surface. Based on Radix Tabs for accessibility and focus management.

Overview

Use for

  • Segmenting dashboard content (overview, analytics, billing).
  • Switching between media views (grid vs. list).
  • Form wizards where each tab is a step with persistent context.

Avoid for

  • Navigation across distinct pages—use sidebar or top nav.
  • More than six options; tabs should remain scannable without scrolling.
  • Asynchronous content loads without skeletons—use inline loaders.

Examples

Dashboard switcher

Account overview
Latest stats across Remix projects.
42 new remixes today
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"

export function DashboardTabs() {
  return (
    <Tabs defaultValue="overview" className="w-full">
      <TabsList>
        <TabsTrigger value="overview">Overview</TabsTrigger>
        <TabsTrigger value="photos">Photos</TabsTrigger>
        <TabsTrigger value="activity">Activity</TabsTrigger>
      </TabsList>
      <TabsContent value="overview">Overview content</TabsContent>
      <TabsContent value="photos">Photo grid</TabsContent>
      <TabsContent value="activity">Activity logs</TabsContent>
    </Tabs>
  )
}

Controlled state

Overview content
import { useState } from "react"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"

export function ControlledTabs() {
  const [value, setValue] = useState("overview")

  return (
    <Tabs value={value} onValueChange={setValue}>
      <TabsList>
        <TabsTrigger value="overview">Overview</TabsTrigger>
        <TabsTrigger value="billing">Billing</TabsTrigger>
      </TabsList>
      <TabsContent value="overview">Overview content</TabsContent>
      <TabsContent value="billing">Billing content</TabsContent>
    </Tabs>
  )
}

Props

PropTypeDefaultDescription
defaultValuestringInitial active tab value for uncontrolled usage.
valuestringControlled active tab value.
onValueChange(value: string) => voidCallback fired when a different tab is selected.

Accessibility

Keyboard

  • Use arrow keys to move between tabs; Tab moves focus into the active panel.
  • Radix sets `role="tablist"`, `tab`, and `tabpanel` semantics automatically.
  • Ensure each `TabsTrigger` has a unique value string.

Design guidance

  • Keep labels short (1–2 words) to avoid overflow.
  • Provide persistent context outside the tab list so the user knows what is switching.
  • For mobile, consider wrapping triggers to a scrollable container with `overflow-x-auto`.