Tabs
stimeo--tabs
Tabbed panels with automatic activation, roving tabindex, and arrow-key navigation.
The stimeo--tabs controller implements the WAI-ARIA Tabs pattern with automatic activation: moving focus with the arrow keys immediately selects the focused tab. It manages aria-selected, a roving tabindex (0 for the active tab, -1 for the rest), and panel visibility. Tabs pair to panels by index. The library provides behavior only — styling is owned by this Playground.
Keyboard
| Key | Action |
|---|---|
| → / ← | Select the next / previous tab (wrapping). |
| Home / End | Select the first / last tab. |
<%# Markup for the tabs (APG Tabs / automatic activation) demo.
stimeo--tabs provides aria-selected, roving tabindex, arrow-key switching, and panel
visibility control. The initially selected tab has aria-selected="true" and
non-selected panels carry hidden (to prevent SSR flicker). %>
<div class="tabs" data-controller="stimeo--tabs">
<div class="tabs__list" role="tablist" aria-label="<%= t("components.tabs.demo.label") %>">
<button class="tabs__tab" role="tab" id="tab-overview" aria-controls="panel-overview"
aria-selected="true"
data-stimeo--tabs-target="tab"
data-action="click->stimeo--tabs#select
keydown->stimeo--tabs#onKeydown"><%= t(
"components.tabs.demo.overview"
) %></button>
<button class="tabs__tab" role="tab" id="tab-profile" aria-controls="panel-profile"
aria-selected="false" tabindex="-1"
data-stimeo--tabs-target="tab"
data-action="click->stimeo--tabs#select
keydown->stimeo--tabs#onKeydown"><%= t(
"components.tabs.demo.profile"
) %></button>
<button class="tabs__tab" role="tab" id="tab-billing" aria-controls="panel-billing"
aria-selected="false" tabindex="-1"
data-stimeo--tabs-target="tab"
data-action="click->stimeo--tabs#select
keydown->stimeo--tabs#onKeydown"><%= t(
"components.tabs.demo.billing"
) %></button>
</div>
<div class="tabs__panel" role="tabpanel" id="panel-overview" aria-labelledby="tab-overview"
data-stimeo--tabs-target="panel"><%= t("components.tabs.demo.overview_content") %></div>
<div class="tabs__panel" role="tabpanel" id="panel-profile" aria-labelledby="tab-profile"
data-stimeo--tabs-target="panel" hidden><%= t(
"components.tabs.demo.profile_content"
) %></div>
<div class="tabs__panel" role="tabpanel" id="panel-billing" aria-labelledby="tab-billing"
data-stimeo--tabs-target="panel" hidden><%= t(
"components.tabs.demo.billing_content"
) %></div>
</div>
/*
* Presentation-only styles for the tabs demo.
* The selected state is expressed via aria-selected (set by the library).
*/
.tabs__list {
display: flex;
gap: 0.25rem;
border-bottom: 1px solid var(--border);
}
.tabs__tab {
padding: 0.5rem 1rem;
font-size: 0.95rem;
cursor: pointer;
color: var(--muted);
background: transparent;
border: 0;
border-bottom: 2px solid transparent;
}
.tabs__tab[aria-selected="true"] {
color: var(--fg);
border-bottom-color: var(--accent);
}
.tabs__panel {
padding: 1rem 0.25rem;
}
This demo needs no consumer-side JS (the controller handles the behavior).
These demo styles use shared design tokens (light + dark). Copy the shared styles too, then toggle data-theme on your root element for dark mode.
The data-* attributes you add to your own HTML to wire this component. Put the data-controller below on a root element, then place its targets / values / actions inside that element.
On the root element
data-controller="stimeo--tabs"
Targets
| Name | Description | Attribute |
|---|---|---|
tab
required
|
A tab button (role=tab) paired to the panel of the same index; carries aria-selected and roving tabindex. |
data-stimeo--tabs-target="tab" |
panel
required
|
A tab panel (role=tabpanel); its hidden attribute reflects whether its tab is selected. |
data-stimeo--tabs-target="panel" |
Actions
| Name | Description | Action |
|---|---|---|
onKeydown
|
Arrow/Home/End navigation with automatic activation: focusing a tab selects it. | stimeo--tabs#onKeydown |
select
|
Selects the clicked tab, updating aria-selected, tabindex, and panel visibility. |
stimeo--tabs#select |
State hooks
The library only manages these ARIA/data attributes and custom properties. Your CSS reads them to render the look — selectors like [aria-selected], [aria-expanded], or var(--stimeo--…) hook into this state.
| Hook | Target | Meaning |
|---|---|---|
aria-selected |
Each tab | "true" on the active tab, "false" on the others. |
tabindex |
Each tab | 0 on the active tab, -1 on the others (roving tabindex). |
hidden |
Each panel | Only the selected tab's panel is shown. |