elements
Demos UI Feed Discuss Install
Style System
  • Style
  • Themes
  • Behaviors
Forms
  • Button
  • ButtonGroup
  • Checkbox
  • ColorPicker
  • Combobox
  • Field
  • FileUpload
  • Input
  • InputGroup
  • InputOTP
  • Label
  • NativeSelect
  • NumberInput
  • PasswordInput
  • RadioGroup
  • Select
  • Slider
  • Switch
  • TagsInput
  • Textarea
  • Toggle
  • ToggleGroup
Overlays
  • AlertDialog
  • Command
  • ContextMenu
  • Dialog
  • Drawer
  • DropdownMenu
  • HoverCard
  • Menubar
  • NavigationMenu
  • Popover
  • Sheet
  • Toast
  • Tooltip
Display
  • Accordion
  • Alert
  • AspectRatio
  • Avatar
  • Badge
  • Breadcrumb
  • Card
  • Collapsible
  • Empty
  • Item
  • Kbd
  • Pagination
  • Progress
  • Separator
  • Skeleton
  • Spinner
  • Table
  • Tabs
  • Tree
Layout
  • Carousel
  • Resizable
  • ScrollArea
  • Sidebar
Dates
  • Calendar
  • DatePicker
  • DateRangePicker
  • TimePicker
Data
  • AreaChart
  • BarChart
  • Chart
  • DataTable
  • Kanban
  • LineChart
  • PieChart
  • RadarChart
  • RadialChart
  • VirtualList
Chat
  • Attachment
  • Message
  • MessageList
  • PromptInput
Elements UI / Display / Tabs

Tabs

Layered panels, one shown at a time, chosen from a list of tabs.

Line

The default: an underline marks the active tab.

Overview

You have 12 active projects and 3 pending tasks.

Analytics

Page views are up 25% on last month.

Reports

Three reports are ready to download.

Settings

Notifications, members and billing.

Code
<Tabs value="overview">
  <TabsList label="Project">
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="analytics">Analytics</TabsTrigger>
    <TabsTrigger value="reports">Reports</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview">...</TabsContent>
  ...
</Tabs>

Usage

import { Tabs, TabsList, TabsTrigger, TabsContent } from "@elements/ui/tabs";

<Tabs value="account">
  <TabsList label="Settings">
    <TabsTrigger value="account">Account</TabsTrigger>
    <TabsTrigger value="password">Password</TabsTrigger>
  </TabsList>
  <TabsContent value="account">Make changes to your account here.</TabsContent>
  <TabsContent value="password">Change your password here.</TabsContent>
</Tabs>

Examples

Segmented

variant=segmented: a muted track with the active tab raised. Fields in the other tab keep their values.

Make changes to your account here.

Change your password here.

Code
<Tabs variant="segmented" value="account">
  <TabsList label="Account settings">
    <TabsTrigger value="account">Account</TabsTrigger>
    <TabsTrigger value="password">Password</TabsTrigger>
  </TabsList>
  <TabsContent value="account">...fields...</TabsContent>
  <TabsContent value="password">...fields...</TabsContent>
</Tabs>

Vertical

orientation=vertical stacks the list beside the panel; Up and Down move.

Your name and email.

Change your password.

Email and push settings.

Code
<Tabs orientation="vertical" value="account">
  <TabsList label="Preferences">
    <TabsTrigger value="account">Account</TabsTrigger>
    <TabsTrigger value="password">Password</TabsTrigger>
    <TabsTrigger value="notifications">Notifications</TabsTrigger>
  </TabsList>
  ...
</Tabs>

Disabled

A disabled tab cannot be chosen and arrows skip it.

Home.

Unreachable.

Settings.

Code
<Tabs value="home">
  <TabsList label="Sections">
    <TabsTrigger value="home">Home</TabsTrigger>
    <TabsTrigger value="disabled" disabled>Disabled</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  ...
</Tabs>

Icons

An Icon beside the label sizes itself.

The rendered component.

Its source.

Code
<Tabs variant="segmented" value="preview">
  <TabsList label="View">
    <TabsTrigger value="preview"><Icon name="search" /> Preview</TabsTrigger>
    <TabsTrigger value="code"><Icon name="pencil" /> Code</TabsTrigger>
  </TabsList>
  ...
</Tabs>

Manual activation

activation=manual: arrows move focus, Enter or Space activates.

One.

Two.

Three.

Code
<Tabs activation="manual" value="one">...</Tabs>

Controlled

Bind value to state. Clicks write it back; onValueChange fires only for the user's changes.

value: a onValueChange: 0 (none)

Alpha.

Bravo.

Charlie.

Code
<Tabs value={state.tab} onValueChange={(v) => state.last = v}>...</Tabs>
<Button onclick={() => state.tab = "c"}>Show C</Button>

Guarded

onValueChange runs before the switch with event details (reason, event, cancel). details.cancel() keeps the tab, as an unsaved-changes guard would, until the guard is off.

value: a, attempts: 0, last: none

Unsaved draft.

The rendered preview.

Code
<Tabs value={state.tab} onValueChange={(value, details) => {
  if (state.unsaved && !confirm("Discard your changes?")) details.cancel();
}}>
  <TabsList label="Editor">...</TabsList>
  ...
</Tabs>

Right to left

dir=rtl lays the tabs out from the right and flips the arrows.

لديك ١٢ مشروعًا نشطًا.

التحليلات.

التقارير.

Code
<Tabs dir="rtl" value="overview">...</Tabs>

No starting value, no loop

With no value the first enabled tab is chosen. loop=false stops the arrows at the ends.

Locked.

First step.

Second step.

Last step.

Code
<Tabs loop={false}>
  <TabsList label="Steps">
    <TabsTrigger value="first">First</TabsTrigger>
    ...
  </TabsList>
</Tabs>

No tab active

value null starts with no tab active and no panel. The first tab holds the tab stop; a click or arrow picks one. A placeholder of the panel's size keeps the page still.

Choose a plan to see what it includes.

One project and community support.

Ten projects and shared billing.

Unlimited projects and single sign-on.

Code
<Tabs value={state.plan}>   <!-- null: no tab active -->
  <TabsList label="Plans">
    <TabsTrigger value="starter">Starter</TabsTrigger>
    <TabsTrigger value="team">Team</TabsTrigger>
  </TabsList>
  <p e:if={state.plan === null}>Choose a plan to see what it includes.</p>
  <TabsContent value="starter">...</TabsContent>
  <TabsContent value="team">...</TabsContent>
</Tabs>

Labelled by a heading

attrs on TabsList and TabsContent set aria-labelledby, an id or a test hook, so a visible heading names the tablist.

Team

Four members.

Two pending invites.

Code
<h4 id="team-heading">Team</h4>
<Tabs value="members">
  <TabsList attrs={{ "aria-labelledby": "team-heading" }}>...</TabsList>
  <TabsContent value="members" attrs={{ id: "members-panel" }}>...</TabsContent>
</Tabs>

Closable

With onTabDelete, Delete on a tab closes it and focus moves to the next tab (APG). The caller removes the tab from its list.

Contents of index.ts.

Contents of app.ts.

Contents of style.css.

Contents of README.md.

Code
<Tabs value={state.file} onTabDelete={(v) => state.files = state.files.filter((f) => f !== v)}>
  <TabsList label="Open files">
    <TabsTrigger e:for={f of state.files} e:key={(f) => f} value={f}>{f}</TabsTrigger>
  </TabsList>
  <TabsContent e:for={f of state.files} e:key={(f) => f} value={f}>...</TabsContent>
</Tabs>

Indicator

Each list has a .tab-indicator that tracks the active tab through --active-tab-left, -top, -width and -height. Style it and the line slides between tabs.

Overview.

Projects.

Account settings.

Code
<!-- .tab-indicator gets --active-tab-left, -top, -width and -height -->
<Tabs class="tabs-sliding" value="overview">...</Tabs>

.tabs-sliding { --tab-active-line: transparent; }
.tabs-sliding .tab-indicator {
  top: auto;
  bottom: 0;
  height: var(--tab-line-width);
  background: var(--accent);
  transition: left var(--duration-enter), width var(--duration-enter);
}

Animated panels

data-activation-direction says which way the selection moved. The new panel slides in from that side with data-starting-style while the old one leaves with data-ending-style.

Overview

Twelve active projects.

Projects

Three due this week.

Account

Signed in as Ada.

Code
<Tabs class="tabs-animated" value="overview">...</Tabs>

/* panels share one grid cell; data-activation-direction says which way */
.tabs-animated > .tab-panel { grid-row: 2; grid-column: 1; transition: transform, opacity; }
.tabs-animated > .tab-panel[data-starting-style][data-activation-direction="right"] {
  opacity: 0;
  transform: translateX(var(--space-8));
}

Links

href renders a tab as a link: still role=tab with one tab stop and arrow keys, and following it navigates. Panels are optional.

Overview Billing Security
Code
<Tabs value="overview">
  <TabsList label="Account">
    <TabsTrigger value="overview" href="/account">Overview</TabsTrigger>
    <TabsTrigger value="billing" href="/account/billing">Billing</TabsTrigger>
  </TabsList>
</Tabs>

Nested

Tabs inside a panel run on their own.

Inner A.

Inner B, same value as an outer tab.

Outer B.

Code
<Tabs value="outer-a">
  ...
  <TabsContent value="outer-a">
    <Tabs variant="segmented" value="inner-a">...</Tabs>
  </TabsContent>
</Tabs>

In a form

Inactive panels stay mounted, so their fields submit. A required field left empty in another tab activates that tab on submit, so the browser can point at it.

submitted: 0
Code
<form onsubmit={() => save(state)}>
  <Tabs value="account">
    <TabsList label="Profile">...</TabsList>
    <TabsContent value="account"><input name="name"></TabsContent>
    <TabsContent value="password"><input name="password" type="password" required></TabsContent>
  </Tabs>
  <Button type="submit">Save</Button>
</form>
Reference Tabs in the manual Every part, attribute, slot, key and data attribute.
Prev TableNext Tree
Product
Manual Pricing Compare
Company
History Contact
Legal
Privacy Policy Acceptable Use Terms License Agreement
© Elements Dev Corporation 2026