<pe-tabs class="Tabs">
<div class="List" data-tabs-list aria-label="Account settings">
<button type="button" class="Tab" data-tabs-trigger="profile">Profile</button>
<button type="button" class="Tab" data-tabs-trigger="security">Security</button>
<span class="Indicator" data-tabs-indicator></span>
</div>
<section class="Panel" data-tabs-content="profile">Profile settings</section>
<section class="Panel" data-tabs-content="security">Security settings</section>
</pe-tabs>
.Tabs {
display: block;
max-width: 24rem;
width: 100%;
color: oklch(14.5% 0 0);
@media (prefers-color-scheme: dark) {
color: white;
}
}
.Tabs .List {
position: relative;
display: flex;
gap: 0.25rem;
border-bottom: 1px solid oklch(14.5% 0 0);
@media (prefers-color-scheme: dark) {
border-bottom-color: white;
}
}
.Tabs .Tab {
margin-bottom: -1px;
padding: 0.5rem 0.75rem;
border: 1px solid transparent;
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
&[data-state="active"] {
border-color: oklch(14.5% 0 0);
border-bottom-color: oklch(97% 0 0);
background: oklch(97% 0 0);
@media (prefers-color-scheme: dark) {
border-color: white;
border-bottom-color: oklch(14.5% 0 0);
background: oklch(14.5% 0 0);
}
}
}
.Tabs .Indicator {
position: absolute;
bottom: -1px;
left: var(--active-tab-left);
width: var(--active-tab-width);
height: 2px;
background: oklch(14.5% 0 0);
pointer-events: none;
@media (prefers-color-scheme: dark) {
background: white;
}
}
.Tabs .Panel {
padding: 0.75rem 0.25rem;
line-height: 1.5;
}
.Tabs.Vertical {
display: grid;
grid-template-columns: 9rem minmax(0, 1fr);
gap: 1rem;
}
.Tabs .ListVertical {
flex-direction: column;
align-self: start;
border-right: 1px solid oklch(14.5% 0 0);
border-bottom: 0;
@media (prefers-color-scheme: dark) {
border-right-color: white;
}
}
.Tabs .ListVertical .Tab {
width: 100%;
margin: 0;
text-align: left;
}
@media (max-width: 480px) {
.Tabs.Vertical {
grid-template-columns: 1fr;
}
}
Tabs let users switch between multiple pieces of content, showing one at a time. Give the list an accessible name and use the same non-empty value for each trigger/panel pair. Use manual activation when loading a panel is expensive or delayed.
| Key |
Behavior |
| Left / Right |
Move through horizontal tabs, wrapping at the ends |
| Up / Down |
Move through vertical tabs, wrapping at the ends |
| Home / End |
Move to the first / last enabled tab |
| Enter / Space |
Activate the focused tab in manual mode |
Disabled triggers are skipped. Prefer native disabled on buttons.
| Part |
Selector |
Role |
| Host |
<pe-tabs> |
Lifecycle and selected-value boundary |
| List |
[data-tabs-list] |
Receives role="tablist" and orientation |
| Trigger |
[data-tabs-trigger="value"] |
Receives tab ARIA, roving tabindex, and state |
| Panel |
[data-tabs-content="value"] |
Receives tabpanel ARIA and hidden state |
| Indicator |
[data-tabs-indicator] |
Optional visual marker positioned with CSS variables |
| Name | Description |
|---|
data-tabs-value | Initial and current selected value. Updating it selects the matching enabled tab. |
data-tabs-orientation | "horizontal" (default) or "vertical". Controls ARIA and arrow keys. |
data-tabs-activation | "automatic" (default) or "manual". |
data-tabs-loop | "true" (default) or "false". Controls arrow-key wrapping. |
| Name | Type | Description |
|---|
value | string | null | Gets the current value. Assign a string to select it. |
select(value, options?) | void | Selects an enabled tab. Pass { focus: true } to focus its trigger. |
| Name | Description |
|---|
pe-tabs:change | Bubbles and is composed. Fired when the selected tab changes. |
Event detail fields:
| Name | Description |
|---|
value | New selected value, or null when none. |
previousValue | Previous selected value. |
trigger | Activating tab trigger, when applicable. |
panel | Matching tab panel, when applicable. |
reason | "click", "keyboard", "programmatic", "attribute", "disabled", or "missing". |
activationDirection | "left", "right", "up", "down", or "none". |
| Name | Description |
|---|
data-state | On triggers and panels: "active" or "inactive". On the host: reflects the current value. |
data-activation-direction | On the host and active trigger: "left", "right", "up", "down", or "none". |
data-tabs-indicator | Optional visual marker inside the tab list. Receives positioning CSS variables. |
When [data-tabs-indicator] is present, the component sets:
| Name | Description |
|---|
--active-tab-left | Distance from the list's left edge. |
--active-tab-right | Distance from the list's right edge. |
--active-tab-top | Distance from the list's top edge. |
--active-tab-bottom | Distance from the list's bottom edge. |
--active-tab-width | Active trigger width. |
--active-tab-height | Active trigger height. |