Skip to content

Tabs

Overview content.
Activity content.
Settings content.
<div class="tabs">
<div class="tab-list" role="tablist">
<input
class="tab-input"
type="radio"
name="basic"
id="basic-1"
value="1"
checked
/>
<label class="tab" for="basic-1">Overview</label>
<input
class="tab-input"
type="radio"
name="basic"
id="basic-2"
value="2"
/>
<label class="tab" for="basic-2">Activity</label>
<input
class="tab-input"
type="radio"
name="basic"
id="basic-3"
value="3"
/>
<label class="tab" for="basic-3">Settings</label>
</div>
<div class="tab-panel" data-value="1">Overview content.</div>
<div class="tab-panel" data-value="2">Activity content.</div>
<div class="tab-panel" data-value="3">Settings content.</div>
</div>
Daily breakdown.
Weekly breakdown.
Monthly breakdown.
<div class="tabs tabs-boxed tabs-sm">
<div class="tab-list" role="tablist">
<input
class="tab-input"
type="radio"
name="boxed"
id="boxed-1"
value="1"
/>
<label class="tab" for="boxed-1">Day</label>
<input
class="tab-input"
type="radio"
name="boxed"
id="boxed-2"
value="2"
checked
/>
<label class="tab" for="boxed-2">Week</label>
<input
class="tab-input"
type="radio"
name="boxed"
id="boxed-3"
value="3"
/>
<label class="tab" for="boxed-3">Month</label>
</div>
<div class="tab-panel" data-value="1">Daily breakdown.</div>
<div class="tab-panel" data-value="2">Weekly breakdown.</div>
<div class="tab-panel" data-value="3">Monthly breakdown.</div>
</div>
Daily breakdown.
Weekly breakdown.
Monthly breakdown.
<div class="tabs tabs-boxed tabs-primary tabs-sm">
<div class="tab-list" role="tablist">
<input
class="tab-input"
type="radio"
name="boxed-primary"
id="boxed-primary-1"
value="1"
/>
<label class="tab" for="boxed-primary-1">Day</label>
<input
class="tab-input"
type="radio"
name="boxed-primary"
id="boxed-primary-2"
value="2"
checked
/>
<label class="tab" for="boxed-primary-2">Week</label>
<input
class="tab-input"
type="radio"
name="boxed-primary"
id="boxed-primary-3"
value="3"
/>
<label class="tab" for="boxed-primary-3">Month</label>
</div>
<div class="tab-panel" data-value="1">Daily breakdown.</div>
<div class="tab-panel" data-value="2">Weekly breakdown.</div>
<div class="tab-panel" data-value="3">Monthly breakdown.</div>
</div>
Inbox content.
Archive content.
Spam content.
<div class="tabs tabs-full-width">
<div class="tab-list" role="tablist">
<input
class="tab-input"
type="radio"
name="full"
id="full-1"
value="1"
checked
/>
<label class="tab" for="full-1">Inbox</label>
<input class="tab-input" type="radio" name="full" id="full-2" value="2" />
<label class="tab" for="full-2">Archive</label>
<input class="tab-input" type="radio" name="full" id="full-3" value="3" />
<label class="tab" for="full-3">Spam</label>
</div>
<div class="tab-panel" data-value="1">Inbox content.</div>
<div class="tab-panel" data-value="2">Archive content.</div>
<div class="tab-panel" data-value="3">Spam content.</div>
</div>
Daily breakdown.
Weekly breakdown.
Monthly breakdown.
<div class="tabs tabs-boxed tabs-full-width">
<div class="tab-list" role="tablist">
<input
class="tab-input"
type="radio"
name="boxed-full"
id="boxed-full-1"
value="1"
/>
<label class="tab" for="boxed-full-1">Day</label>
<input
class="tab-input"
type="radio"
name="boxed-full"
id="boxed-full-2"
value="2"
checked
/>
<label class="tab" for="boxed-full-2">Week</label>
<input
class="tab-input"
type="radio"
name="boxed-full"
id="boxed-full-3"
value="3"
/>
<label class="tab" for="boxed-full-3">Month</label>
</div>
<div class="tab-panel" data-value="1">Daily breakdown.</div>
<div class="tab-panel" data-value="2">Weekly breakdown.</div>
<div class="tab-panel" data-value="3">Monthly breakdown.</div>
</div>
Variant A.
Variant B.
Variant C.
<div style="max-width: 22rem">
<div class="tabs tabs-boxed tabs-sm tabs-wrap">
<div class="tab-list" role="tablist">
<input
class="tab-input"
type="radio"
name="wrap"
id="wrap-1"
value="1"
checked
/>
<label class="tab" for="wrap-1">1 click, 10s on page</label>
<input
class="tab-input"
type="radio"
name="wrap"
id="wrap-2"
value="2"
/>
<label class="tab" for="wrap-2">0 clicks, 5s on page</label>
<input
class="tab-input"
type="radio"
name="wrap"
id="wrap-3"
value="3"
/>
<label class="tab" for="wrap-3">1 click, no automatic popup</label>
</div>
<div class="tab-panel" data-value="1">Variant A.</div>
<div class="tab-panel" data-value="2">Variant B.</div>
<div class="tab-panel" data-value="3">Variant C.</div>
</div>
</div>
Grid view.
List view.
Chart view.
<div class="tabs tabs-boxed tabs-sm">
<div class="tab-list" role="tablist">
<input
class="tab-input"
type="radio"
name="icons"
id="icons-1"
value="1"
checked
/>
<label class="tab" for="icons-1"
><i class="ti ti-layout-grid" aria-hidden="true"></i> Grid</label
>
<input
class="tab-input"
type="radio"
name="icons"
id="icons-2"
value="2"
/>
<label class="tab" for="icons-2"
><i class="ti ti-list" aria-hidden="true"></i> List</label
>
<input
class="tab-input"
type="radio"
name="icons"
id="icons-3"
value="3"
/>
<label class="tab" for="icons-3"
><i class="ti ti-chart-bar" aria-hidden="true"></i> Chart</label
>
</div>
<div class="tab-panel" data-value="1">Grid view.</div>
<div class="tab-panel" data-value="2">List view.</div>
<div class="tab-panel" data-value="3">Chart view.</div>
</div>
Profile settings.
Account settings.
Billing details.
Personal access tokens.
<div class="tabs" data-orientation="vertical">
<div class="tab-list" role="tablist">
<input
class="tab-input"
type="radio"
name="vertical"
id="vertical-1"
value="1"
checked
/>
<label class="tab" for="vertical-1">Profile</label>
<input
class="tab-input"
type="radio"
name="vertical"
id="vertical-2"
value="2"
/>
<label class="tab" for="vertical-2">Account</label>
<input
class="tab-input"
type="radio"
name="vertical"
id="vertical-3"
value="3"
/>
<label class="tab" for="vertical-3">Billing</label>
<input
class="tab-input"
type="radio"
name="vertical"
id="vertical-4"
value="4"
/>
<label class="tab" for="vertical-4">API keys</label>
</div>
<div class="tab-panel" data-value="1">Profile settings.</div>
<div class="tab-panel" data-value="2">Account settings.</div>
<div class="tab-panel" data-value="3">Billing details.</div>
<div class="tab-panel" data-value="4">Personal access tokens.</div>
</div>
Part Renders Class
Tabs <div> tabs
Tabs.List <div> tab-list
Tabs.Tab <button> tab
Tabs.Panel <div> tab-panel
Part Prop Type Default
Tabs variant "bordered" | "boxed" "bordered"
Tabs size "sm" | "md" | "lg" "md"
Tabs fullWidth boolean false
Tabs wrap boolean false
Tabs primary boolean false
Tabs.Tab icon IconProp

primary only affects variant="boxed". Wraps Base UI Tabs, which owns value / defaultValue / onValueChange and orientation, and supplies the role="tablist" wiring plus arrow-key navigation. Tabs.Tab and Tabs.Panel are matched by value, which can be any string. Plus native <div> attributes.

Prefer React over the vanilla pattern past six panels — see below.

Class Effect
tabs Root column. Scopes every selector below, so a stray class="tab" elsewhere is unaffected
tab-list Tab row with a bottom border, 0.25rem gap
tab 2.25rem tall, 0.75rem side padding, text-sm medium, muted until selected
tab-panel 0.75rem of top padding; hidden unless its data-value matches the checked input
tab-input The visually-hidden radio driving selection
tabs-boxed Segmented control: bordered 0.375rem box, muted fill, a thumb behind the active label
tabs-primary Fills that thumb with the primary colour. Boxed only
tabs-full-width List spans the container, tabs share the row evenly
tabs-wrap List flows onto new rows, each label staying on one line
tabs-sm 1.75rem tall tabs, text-xs
tabs-lg 2.75rem tall tabs, text-base

There is no tabs-bordered or tabs-md — both are the unmodified tabs. data-orientation="vertical" on the root turns the rail vertical, moving the border and the marker to the trailing edge.

Selection is a radio group: one tab-input per tab sharing a name, and tab-panel[data-value] matched to the input’s value — so switching needs no JavaScript. The trade-off is a hard cap: the panel-matching rules are enumerated for values 16, so a seventh panel never shows. Past six, use React.

Both variants share one marker mechanism: the selected tab becomes a CSS anchor and a single tab-list pseudo-element tracks it, which the browser interpolates — so the underline slides between tabs, and the boxed thumb slides behind the labels, with no extra DOM and no JavaScript in either bundle. Where anchor positioning is missing, a per-tab fallback scales an underline in or crossfades the thumb instead. The slide is dropped under prefers-reduced-motion: reduce.

Write role="tablist" yourself; the label-and-radio pattern gives keyboard support for free but not the tab ARIA. Selected state is read from [data-selected], [aria-selected="true"], or a checked tab-input, so all three markup styles land on the same visuals.

tabs-wrap earns its place on variable-length free-text labels in a narrow container: without it, a boxed control squishes them to equal slivers.

For a group of independent toggles rather than a single choice, use a button group.