ComponentsNavigationTabs

Tabs

9 variants · horizontal + vertical · badge · icon · subtitle · scrollable · step

UnderlinePillFilledBorderedCardFloatingBubbleGradientVertical (line)ScrollableStepTabsWith subtitle

Overview

Tabs organize peer views within the same context and let people move between them without leaving the page.

Related views

Live preview

The active tab is visually and programmatically connected to its panel.

Content for Overview

ProjectTabs.tsx
tsx
<Tabs variant="underline" defaultValue="overview">
<TabsList>
<TabsTrigger value="overview" icon="info">Overview</TabsTrigger>
<TabsTrigger value="analytics" badge={3}>Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
<TabsTrigger value="messages" badge={12}>Messages</TabsTrigger>
<TabsTrigger value="settings" disabled>Disabled</TabsTrigger>
</TabsList>
<TabsContent value="overview"><p>Overview content goes here.</p></TabsContent>
<TabsContent value="analytics"><p>Analytics content goes here.</p></TabsContent>
<TabsContent value="reports"><p>Reports content goes here.</p></TabsContent>
<TabsContent value="messages"><p>Messages content goes here.</p></TabsContent>
</Tabs>

Anatomy

Tabs consist of a tablist, ordered tab triggers, an active indicator, and one associated content panel.

OverviewActivitySettings
Overview panel content
1234
  1. 1
    TablistRequired

    Groups peer tabs and declares horizontal or vertical orientation.

  2. 2
    Tab triggerRequired

    Names and activates one corresponding panel.

  3. 3
    Active indicatorRequired

    Distinguishes the selected tab without relying on color alone.

  4. 4
    Tab panelRequired

    Contains the view controlled by the selected trigger.

When to use

Use tabs when a small set of peer views belongs to one page context.

Recommended

  • Switch between related views

    Keep the page title and primary context stable while panel content changes.

  • Organize manageable content

    Use concise labels for a small, predictable set of categories.

  • Preserve quick comparison

    Let people move between views without a full navigation transition.

When not to use

Choose navigation or progressive disclosure when content is not peer-level.

Avoid

  • Do not use for destinations

    Use navigation links when each item has its own page, URL, or information hierarchy.

  • Do not use for sequential tasks

    Use a stepper for required ordered progress; StepTabs communicates progress, not peer views.

  • Do not hide critical content

    Use sections or Accordion when people need to read content together.

Variants

Visual variants must preserve the same tab semantics and interaction model.

Underline

Default choice for page-level peer views.

Pill and filled

Compact choice for filters and tightly scoped views.

Vertical line

Useful when labels are longer or the view count is larger.

StepTabs

Shows progress through a sequential workflow.

States

Each trigger communicates selection, focus, availability, and optional supporting status.

StateTriggerVisual responseInteraction
InactiveAnother tab is selectedNeutral labelCan receive focus and activate
HoverPointer enters an enabled tabSubtle emphasisSignals availability
FocusKeyboard navigationVisible focus indicatorArrow keys move focus
ActiveTab is selectedIndicator and emphasized labelIts panel is displayed
DisabledView is unavailableReduced emphasisSkipped by keyboard navigation
CompleteStepTabs step is finishedCompletion mark and connectorMay remain revisitable

Behavior

Tabs use roving focus and keep every trigger associated with exactly one panel.

Focus movement

Arrow keys move focus through enabled tabs and wrap at list boundaries.

Panel relationship

Trigger and panel share a stable value used for IDs and ARIA references.

Overflow

Allow horizontal scrolling rather than shrinking labels beyond readability.

Mounted state

Use keepMounted only when inactive panel state must persist.

Accessibility

Tabs follow the ARIA tab pattern with one tabbable trigger, directional navigation, and labelled tab panels.

KeyAction
TabMoves focus into the active tab, then into the active panel.
←→Moves focus across horizontal tabs.
↑↓Moves focus across vertical tabs.
HomeEndMoves focus to the first or last enabled tab.
  • Use concise, unique tab labels.
  • Keep one trigger in the page tab order.
  • Expose selected state with aria-selected.
  • Connect every trigger and panel with stable IDs.
  • Declare vertical orientation when applicable.
  • Do not use disabled tabs to advertise unavailable features without explanation.

Content guidelines

Tab labels describe the view—not the action of opening it.

Use short nouns

Name the content category with one or two familiar words.

ExampleOverview

Keep labels parallel

Use the same grammatical pattern and level of specificity.

ExampleOverview · Activity · Settings

Avoid instructions

Do not add “View,” “Open,” or “Go to” before labels.

ExampleBilling

Use badges sparingly

Show actionable counts or status, not decorative metrics.

ExampleMessages 12

Examples

The examples below cover the supported visual treatments, orientations, overflow, subtitles, and sequential StepTabs pattern.

Underline (M3 primary)

Live preview

Classic bottom-border active indicator — icon, badge, disabled states

Content for Overview

App.tsx
tsx
<Tabs variant="underline" defaultValue="overview">
<TabsList>
<TabsTrigger value="overview" icon="info">Overview</TabsTrigger>
<TabsTrigger value="analytics" badge={3}>Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
<TabsTrigger value="messages" badge={12}>Messages</TabsTrigger>
<TabsTrigger value="settings" disabled>Disabled</TabsTrigger>
</TabsList>
<TabsContent value="overview"><p>Overview content goes here.</p></TabsContent>
<TabsContent value="analytics"><p>Analytics content goes here.</p></TabsContent>
<TabsContent value="reports"><p>Reports content goes here.</p></TabsContent>
<TabsContent value="messages"><p>Messages content goes here.</p></TabsContent>
</Tabs>

Pill / Segmented

Live preview

Floating pill on a tinted background — great for filter controls

Content for All

App.tsx
tsx
<Tabs variant="pill" defaultValue="all">
<TabsList>
<TabsTrigger value="all">All</TabsTrigger>
<TabsTrigger value="active">Active</TabsTrigger>
<TabsTrigger value="paused">Paused</TabsTrigger>
<TabsTrigger value="completed">Completed</TabsTrigger>
</TabsList>
<TabsContent value="all"><p>All items shown here.</p></TabsContent>
<TabsContent value="active"><p>Active items shown here.</p></TabsContent>
<TabsContent value="paused"><p>Paused items shown here.</p></TabsContent>
<TabsContent value="completed"><p>Completed items shown here.</p></TabsContent>
</Tabs>

Filled

Live preview

Solid primary background on the active tab

Content for Design

App.tsx
tsx
<Tabs variant="filled" defaultValue="design">
<TabsList>
<TabsTrigger value="design">Design</TabsTrigger>
<TabsTrigger value="engineering">Engineering</TabsTrigger>
<TabsTrigger value="marketing">Marketing</TabsTrigger>
<TabsTrigger value="product">Product</TabsTrigger>
</TabsList>
<TabsContent value="design"><p>Design team content.</p></TabsContent>
<TabsContent value="engineering"><p>Engineering team content.</p></TabsContent>
<TabsContent value="marketing"><p>Marketing team content.</p></TabsContent>
<TabsContent value="product"><p>Product team content.</p></TabsContent>
</Tabs>

Bordered

Live preview

Outlined active tab — clean and minimal

Content for Day

App.tsx
tsx
<Tabs variant="bordered" defaultValue="day">
<TabsList>
<TabsTrigger value="day">Day</TabsTrigger>
<TabsTrigger value="week">Week</TabsTrigger>
<TabsTrigger value="month">Month</TabsTrigger>
<TabsTrigger value="year">Year</TabsTrigger>
</TabsList>
<TabsContent value="day"><p>Daily view.</p></TabsContent>
<TabsContent value="week"><p>Weekly view.</p></TabsContent>
<TabsContent value="month"><p>Monthly view.</p></TabsContent>
<TabsContent value="year"><p>Yearly view.</p></TabsContent>
</Tabs>

Card tabs

Live preview

Each tab styled as an individual card — supports icons

Content for Analytics

App.tsx
tsx
<Tabs variant="card" defaultValue="analytics">
<TabsList>
<TabsTrigger value="analytics" icon="info">Analytics</TabsTrigger>
<TabsTrigger value="team" icon="info">Team</TabsTrigger>
<TabsTrigger value="settings" icon="settings">Settings</TabsTrigger>
<TabsTrigger value="billing" icon="info">Billing</TabsTrigger>
</TabsList>
<TabsContent value="analytics"><p>Analytics dashboard content.</p></TabsContent>
<TabsContent value="team"><p>Team management content.</p></TabsContent>
<TabsContent value="settings"><p>Settings content.</p></TabsContent>
<TabsContent value="billing"><p>Billing content.</p></TabsContent>
</Tabs>

Floating pill

Live preview

Elevated floating appearance with shadow on the active tab

Content for Overview

App.tsx
tsx
<Tabs variant="floating" defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
<TabsContent value="overview"><p>Overview content.</p></TabsContent>
<TabsContent value="analytics"><p>Analytics content.</p></TabsContent>
<TabsContent value="reports"><p>Reports content.</p></TabsContent>
<TabsContent value="settings"><p>Settings content.</p></TabsContent>
</Tabs>

Bubble

Live preview

Rounded bubble selection on a tinted container

Content for All posts

App.tsx
tsx
<Tabs variant="bubble" defaultValue="all">
<TabsList>
<TabsTrigger value="all">All posts</TabsTrigger>
<TabsTrigger value="published">Published</TabsTrigger>
<TabsTrigger value="drafts">Drafts</TabsTrigger>
<TabsTrigger value="archived">Archived</TabsTrigger>
</TabsList>
<TabsContent value="all"><p>All posts content.</p></TabsContent>
<TabsContent value="published"><p>Published posts.</p></TabsContent>
<TabsContent value="drafts"><p>Draft posts.</p></TabsContent>
<TabsContent value="archived"><p>Archived posts.</p></TabsContent>
</Tabs>

Gradient active

Live preview

Gradient background on the active tab — great for pricing toggles

Content for Monthly

App.tsx
tsx
<Tabs variant="gradient" defaultValue="monthly">
<TabsList>
<TabsTrigger value="monthly">Monthly</TabsTrigger>
<TabsTrigger value="yearly">Yearly</TabsTrigger>
<TabsTrigger value="lifetime">Lifetime</TabsTrigger>
</TabsList>
<TabsContent value="monthly"><p>Monthly pricing plans.</p></TabsContent>
<TabsContent value="yearly"><p>Yearly pricing — save 20%.</p></TabsContent>
<TabsContent value="lifetime"><p>Lifetime access — one-time payment.</p></TabsContent>
</Tabs>

Vertical (line variant)

Live preview

orientation=vertical stacks the tab list on the left — icons supported

Profile settings

Manage your profile information.

App.tsx
tsx
<Tabs variant="line" orientation="vertical" defaultValue="profile">
<TabsList>
<TabsTrigger value="profile" icon="info">Profile</TabsTrigger>
<TabsTrigger value="notifications" icon="bell">Notifications</TabsTrigger>
<TabsTrigger value="security" icon="lock">Security</TabsTrigger>
<TabsTrigger value="billing" icon="info">Billing</TabsTrigger>
<TabsTrigger value="advanced" icon="settings">Advanced</TabsTrigger>
</TabsList>
<TabsContent value="profile">
<p className="font-medium">Profile settings</p>
<p>Manage your profile information.</p>
</TabsContent>
<TabsContent value="notifications">
<p className="font-medium">Notification preferences</p>
<p>Control how you receive notifications.</p>
</TabsContent>
{/* … other content panels */}
</Tabs>

Scrollable

Live preview

Overflow tabs scroll horizontally — scroll with mouse wheel or drag

Active: React— scroll left/right to see all tabs

App.tsx
tsx
const scrollRef = useRef<HTMLDivElement>(null)
 
function handleWheel(e: React.WheelEvent<HTMLDivElement>) {
e.preventDefault()
if (scrollRef.current) scrollRef.current.scrollLeft += e.deltaY
}
 
<div style={{ width: 300, overflow: 'hidden' }}>
<div
ref={scrollRef}
onWheel={handleWheel}
style={{
display: 'flex',
overflowX: 'scroll',
borderBottom: '2px solid var(--color-border)',
scrollbarWidth: 'none',
}}
>
{['React', 'TypeScript', 'Tailwind', 'Next.js',
'Vue', 'Angular', 'Svelte', 'Remix', 'Astro'].map(t => (
<button
key={t}
type="button"
style={{ flexShrink: 0, padding: '8px 16px', whiteSpace: 'nowrap' }}
>
{t}
</button>
))}
</div>
</div>

StepTabs — wizard

Live preview

Progress indicator for multi-step flows — done, active, and disabled states

Current step: profile

App.tsx
tsx
import { StepTabs } from 'omverse-ui'
 
const steps = [
{ value: 'account', label: 'Account', done: true },
{ value: 'profile', label: 'Profile' },
{ value: 'plan', label: 'Plan' },
{ value: 'done', label: 'Done', disabled: true },
]
 
const [step, setStep] = useState('profile')
 
<StepTabs value={step} onChange={setStep} steps={steps} />
 
<div style={{ marginTop: 16, display: 'flex', gap: 12 }}>
<button onClick={() => {
const idx = steps.findIndex(s => s.value === step)
if (idx > 0) setStep(steps[idx - 1].value)
}}>← Back</button>
<button onClick={() => {
const idx = steps.findIndex(s => s.value === step)
const next = steps[idx + 1]
if (next && !next.disabled) setStep(next.value)
}}>Next →</button>
</div>

With subtitle

Live preview

subtitle prop adds a small secondary line below each tab label

Content for Overview

App.tsx
tsx
<Tabs variant="underline" defaultValue="overview">
<TabsList>
<TabsTrigger value="overview" subtitle="All metrics">Overview</TabsTrigger>
<TabsTrigger value="revenue" subtitle="$48.2k">Revenue</TabsTrigger>
<TabsTrigger value="users" subtitle="2,841 active">Users</TabsTrigger>
<TabsTrigger value="conversion" subtitle="3.2%">Conversion</TabsTrigger>
</TabsList>
<TabsContent value="overview"><p>All metrics overview.</p></TabsContent>
<TabsContent value="revenue"><p>Revenue breakdown.</p></TabsContent>
<TabsContent value="users"><p>User analytics.</p></TabsContent>
<TabsContent value="conversion"><p>Conversion funnel.</p></TabsContent>
</Tabs>

Props / API

Tabs is the state provider; TabsList, TabsTrigger, and TabsContent form the compound structure. StepTabs exposes a separate progress API.

Tabs props

Props

PropTypeDefaultDescription
defaultValuestring—Initially active tab value (uncontrolled)
valuestring—Controlled active tab value
onValueChange(value: string) => void—Callback fired when the active tab changes
variant'underline' | 'line' | 'pill' | 'filled' | 'bordered' | 'card' | 'floating' | 'bubble' | 'gradient''underline'Visual style of the tab list
orientation'horizontal' | 'vertical''horizontal'Tab list direction
size'sm' | 'md' | 'lg''md'Size of tab labels
childrenReactNode—TabsList + TabsContent components

StepTabs props

Props

PropTypeDefaultDescription
valuestring—Controlled active step value
onChange(value: string) => void—Callback fired when the active step changes
steps{ value: string; label: string; done?: boolean; disabled?: boolean }[]—Step definitions