Components
Tabs
The Tabs are radio inputs and the Panels are just divs that show and hide based on the radio inputs' :checked state.
Full supportSupported since v123.Full supportSupported since v120.Full supportSupported since v17.5.
Anatomy
Profile settings and information.
General account settings.
.ui-tabsContainer element.
.ui-tab-inputA visually hidden radio input that holds a tab's state.
.ui-tab-labelA tab.
.ui-tab-panelThe panel of the selected tab.
Basics
Profile settings and information.
General account settings.
Manage your notifications.
<div class="ui-tabs" role="tablist"> <input type="radio" name="basic-tabs" id="tab-profile" class="ui-tab-input" checked aria-controls="panel-profile" /> <label for="tab-profile" class="ui-tab-label" role="tab">Profile</label> <div id="panel-profile" class="ui-tab-panel" role="tabpanel" aria-labelledby="tab-profile" > Profile settings and information. </div>
<input type="radio" name="basic-tabs" id="tab-settings" class="ui-tab-input" aria-controls="panel-settings" /> <label for="tab-settings" class="ui-tab-label" role="tab">Settings</label> <div id="panel-settings" class="ui-tab-panel" role="tabpanel" aria-labelledby="tab-settings" > General account settings. </div>
<input type="radio" name="basic-tabs" id="tab-notifications" class="ui-tab-input" aria-controls="panel-notifications" /> <label for="tab-notifications" class="ui-tab-label" role="tab" >Notifications</label > <div id="panel-notifications" class="ui-tab-panel" role="tabpanel" aria-labelledby="tab-notifications" > Manage your notifications. </div></div>Accessibility
The tab system uses standard radio inputs and labels, so we get group management and keyboard support for free!
Tab List
| Element | Attribute | Description |
|---|---|---|
.ui-tabs | role="tablist" | Identifies the element as a container for a set of tabs. |
input | name | Groups the radio buttons together for exclusive selection. |
label | role="tab" | Identifies the element as a tab to assistive technology. |
Tab Panel
The content area associated with a tab:
| Attribute | Value | Description |
|---|---|---|
role | "tabpanel" | Identifies the element as a tab panel. |
aria-labelledby | string | Links the panel to its trigger ID. |
Keyboard Interaction
- Tab: Moves focus to the active tab trigger (the radio button). Pressing Tab again moves focus out of the tab list to the next focusable element.
- Right Arrow / Down Arrow: Moves focus to the next tab and activates it.
- Left Arrow / Up Arrow: Moves focus to the previous tab and activates it.
API
Tabs API
| Type | Modifiers | Default | Description |
|---|---|---|---|
| Group | .ui-tab-input[name] | - | The name shared by the tab inputs. Generated when omitted. |
Parts
| Part | Description |
|---|---|
.ui-tabs | Container element. |
.ui-tab-input | A visually hidden radio input that holds a tab's state. |
.ui-tab-label | A tab. |
.ui-tab-panel | The panel of the selected tab. |
The root needs role="tablist". Each tab is an input.ui-tab-input[type="radio"], followed by its label.ui-tab-label[role="tab"] and .ui-tab-panel[role="tabpanel"].
Tabs item API
| Type | Modifiers | Default | Description |
|---|---|---|---|
| State | [checked] | - | Selects the tab initially. |
Parts
| Part | Description |
|---|---|
input.ui-tab-input | The radio input for a tab, followed by the tab and the panel. |
Tabs tab API
Parts
| Part | Description |
|---|---|
label.ui-tab-label | The tab. |
Tabs panel API
Parts
| Part | Description |
|---|---|
.ui-tab-panel | The panel. |
Browser support
Full supportSupported since v123.Full supportSupported since v120.Full supportSupported since v17.5.
See also the full browser support guide.
Installation
@layer components.root { :where(.ui-tabs) { --_accent-color: var(--primary); --_bg-color: transparent;
align-items: flex-start; display: flex; flex-wrap: wrap; gap: 0 var(--size-1); position: relative;
& > .ui-tab-input[type="radio"] { /* utils > .ui-sr-only */ /* TODO: use mixin for this when available */ block-size: 1px; clip-path: inset(50%); inline-size: 1px; overflow: hidden; position: absolute; white-space: nowrap;
&:checked { /* Checked Tab */ & + [role="tab"], & + .ui-tab-label { border-block-end-color: var(--_accent-color); color: var(--_accent-color);
/* Checked Panel */ & + [role="tabpanel"], & + .ui-tab-panel { display: block; } } }
&:focus-visible { & + [role="tab"], & + .ui-tab-label { --focus-ring-color: var(--text-muted); --focus-ring-offset: calc(-1 * var(--size-2)); outline: var(--focus-ring-width) var(--focus-ring-style) var(--focus-ring-color); outline-offset: var(--focus-ring-offset); } } }
/* Tab */ & > [role="tab"], & > .ui-tab-label { align-items: center; background-color: transparent; border-bottom: 2px solid transparent; border-radius: 0; color: var(--text-muted); cursor: pointer; display: inline-flex; font-weight: 600; justify-content: center; line-height: var(--font-lineheight-4); order: 1; padding: var(--size-2) var(--size-3); position: relative; transition: color 0.1s; user-select: none;
&:hover { background-color: light-dark( oklch(from var(--_accent-color) calc(l * 0.75) none h / 5%), oklch(from var(--_accent-color) calc(l * 1.25) none h / 5%) ); } }
/* Tab Panel */ & > [role="tabpanel"], & > .ui-tab-panel { display: none; inline-size: 100%; order: 2; } }}