Tabs

A row of tabs over their panels, showing one at a time when its module is loaded and all of them when it is not.

Example

View Code
<div class="tabs">
    <div role="tablist" aria-label="Account">
        <button type="button" role="tab" id="tab-profile" aria-controls="panel-profile">Profile</button>
        <button type="button" role="tab" id="tab-billing" aria-controls="panel-billing">Billing</button>
    </div>
    <section role="tabpanel" id="panel-profile" aria-labelledby="tab-profile">
        <p>Your name and how people reach you.</p>
    </section>
    <section role="tabpanel" id="panel-billing" aria-labelledby="tab-billing">
        <p>Your plan and your invoices.</p>
    </section>
</div>

When to use it

Alternative views of one thing: a profile and its billing, a chart and its table. Not for steps in a sequence, which want a form, and not for hiding content a reader needs to compare against something else.

How it works

A tab list of buttons over their panels. The CSS styles the list and marks the selected tab with the hue on its edge, and it never hides a panel. That is deliberate: a page that does not load tabs.js, or where the script fails, shows every panel under its own tab, so nothing a reader came for is locked away.

Load the module and it pairs every tab with the panel its aria-controls names, shows one, hides the rest, and takes over the keyboard: arrows move selection, Home and End jump to the ends, and only the selected tab is in the tab order, so Tab leaves the list rather than walking it. A link into a hidden panel opens its tab: on load and on every hashchange the module looks for the URL's fragment inside a panel and, if it finds one, selects that panel's tab and brings the target into view, without moving keyboard focus to the tab. Tabs may nest inside another tab's panel: each set keeps its own selection and its own arrow keys, and a link into the inner set opens every enclosing panel on the way. data-emphasis="high" fills the selected tab with the variant instead of underlining it, its top corners (or, in a vertical list, the corners away from the divider) rounded like any other filled control.

<div class="tabs" data-emphasis="high">
    <div role="tablist" aria-label="Report">
        <button type="button" role="tab" id="t-summary" aria-controls="p-summary" aria-selected="true">Summary</button>
        <button type="button" role="tab" id="t-detail" aria-controls="p-detail">Detail</button>
    </div>
    <section role="tabpanel" id="p-summary" aria-labelledby="t-summary"><p>Summary panel.</p></section>
    <section role="tabpanel" id="p-detail" aria-labelledby="t-detail" hidden><p>Detail panel.</p></section>
</div>
<div class="tabs" data-orientation="vertical">
    <div role="tablist" aria-label="Settings">
        <button type="button" role="tab" id="t-general" aria-controls="p-general">General</button>
        <button type="button" role="tab" id="t-keys" aria-controls="p-keys">API keys</button>
    </div>
    <section role="tabpanel" id="p-general" aria-labelledby="t-general"><p>General settings.</p></section>
    <section role="tabpanel" id="p-keys" aria-labelledby="t-keys"><p>Your API keys.</p></section>
</div>

Accessibility

Name the tablist, since a page may have more than one. Each tab's aria-controls names its panel and each panel's aria-labelledby names its tab; the module reads that pairing rather than guessing from document order, so the markup stays the source of truth. The module also maintains aria-selected and the roving tabindex, which is what makes a tab list feel like one control rather than a row of buttons. Without the module the markup is still valid: the roles are right and every panel is readable.

Attributes

Attribute Type Values Default Description
data-variant enum primary, secondary, success, warning, alert, danger, neutral, black, white primary The hue of the selected tab and its edge.
data-orientation enum horizontal, vertical horizontal Whether the tab list runs along the top or down the side.
data-gap enum none, xs, sm, md, lg, xl, 2xl, 3xl, xs-sm, xs-md, xs-lg, xs-xl, xs-2xl, xs-3xl, sm-md, sm-lg, sm-xl, sm-2xl, sm-3xl, md-lg, md-xl, md-2xl, md-3xl, lg-xl, lg-2xl, lg-3xl, xl-2xl, xl-3xl, 2xl-3xl md Space between the list and the panels.
data-emphasis enum high The selected tab is filled with the variant; without it the selected tab is underlined.

Children

  • > [role="tablist"]: exactly 1. The tabs, in order, with a name of its own.
  • [role="tab"]: at least 2. A button whose aria-controls names its panel.
  • > [role="tabpanel"]: at least 2. A panel whose aria-labelledby names its tab.

Tokens

Token Description
--yeti-tabs-border The line under or beside the list.
--yeti-tabs-padding Padding of one tab.
--yeti-border-width Width of that line; the selected mark is twice it.
--yeti-space-md The default gap between the list and the panel.
--yeti-color-primary The default variant's color, when data-variant is absent.
--yeti-color-primary-subtle The default variant's tint.
--yeti-color-primary-soft The default variant's soft stop.
--yeti-color-primary-strong The default variant's strong stop.
--yeti-color-primary-text The default variant's text color.
--yeti-on-primary Text on the default variant's color.
--yeti-space-xs Gap between tabs.
--yeti-weight-strong Weight of a tab's label.
--yeti-duration-fast How long a tab takes to change color.
--yeti-ease The curve of that transition.
--yeti-radius-sm Corner radius of the selected tab with data-emphasis="high".
Internal tokens (may change between minor versions) - `--_yeti-variant` - `--_yeti-variant-subtle` - `--_yeti-variant-text` - `--_yeti-gap` - `--_yeti-variant-soft` - `--_yeti-variant-strong` - `--_yeti-on-variant`

Accessibility

  • Give the tablist a name with aria-label or aria-labelledby. Every tab is a button whose aria-controls names its panel, and every panel names its tab back with aria-labelledby; that pairing is what the module reads. With tabs.js loaded one tab is selected, the rest take tabindex="-1" for a roving focus, and the inactive panels are hidden. Without it nothing is hidden and every tab is focusable, which is plainer but never traps content.
Key Action
Left / Right Moves selection along a horizontal list.
Up / Down Moves selection along a vertical list.
Home / End Selects the first or the last tab.
Tab Leaves the list for the selected panel.

Browser support

  • Used without guards: logical properties
  • Behind @supports: nothing

JavaScript

Optional enhancement: components/tabs/tabs.js. The component works without it.

Each event bubbles, crosses a shadow boundary, and cannot be cancelled.

Event Module Detail Description
yeti:select tabs.js { tab, panel } Dispatched on the .tabs when a click, an arrow key, or a hash reveal selects a tab; never for the pass at load that finds no hash to reveal.

Available since 7.0.0.