Skip to content

Tabs

The ARIA tabs pattern. The initial state is server-rendered; the enhanceTabs behavior adds keyboard navigation. Prefer a self-managing tag? The <re-tabs> custom element wraps this same pattern.

Manage your name and email here.

Live example
<div class="re-tabs" data-re-tabs id="tabs-1">
  <div class="re-tabs__list" role="tablist" aria-label="Account">
    <button
      class="re-tab"
      role="tab"
      id="t-profile"
      aria-controls="p-profile"
      aria-selected="true"
    >
      Profile
    </button>
    <button
      class="re-tab"
      role="tab"
      id="t-security"
      aria-controls="p-security"
      aria-selected="false"
      tabindex="-1"
    >
      Security
    </button>
    <button
      class="re-tab"
      role="tab"
      id="t-billing"
      aria-controls="p-billing"
      aria-selected="false"
      tabindex="-1"
    >
      Billing
    </button>
  </div>

  <section
    class="re-tabpanel"
    role="tabpanel"
    id="p-profile"
    aria-labelledby="t-profile"
    tabindex="0"
  >
    <p>Manage your name and email here.</p>
  </section>
  <section
    class="re-tabpanel"
    role="tabpanel"
    id="p-security"
    aria-labelledby="t-security"
    tabindex="0"
    hidden
  >
    <p>Two-factor authentication, password rotation, sessions.</p>
  </section>
  <section
    class="re-tabpanel"
    role="tabpanel"
    id="p-billing"
    aria-labelledby="t-billing"
    tabindex="0"
    hidden
  >
    <p>Plan, invoices, payment method.</p>
  </section>
</div>
  • KeyboardTab enters the tablist and lands on the selected tab only (roving tabindex: enhanceTabs keeps the active tab at tabindex="0" and the rest at -1); Tab again moves out to the panel. Within the list, ArrowLeft/ArrowRight move focus and activate the tab — automatic activation, wrapping past the ends — Home/End jump to the first/last tab and activate it, and Enter/Space activate the focused tab. Without JavaScript the server-rendered selected panel stays visible and the page remains usable; the keyboard navigation is the only thing the behavior adds.
  • Focus — both the tabs and the active panel show a visible :focus-visible ring (--re-shadow-focus, replacing the default outline). Each panel carries tabindex="0" so keyboard users can focus and scroll a panel that has no focusable children of its own.
  • Semanticsrole="tablist" (labelled with aria-label) wraps role="tab" buttons whose aria-selected marks the current tab and whose aria-controls points at the matching role="tabpanel"; each panel is aria-labelledby its tab, and inactive panels are hidden. Selecting a tab updates aria-selected and toggles hidden, so assistive tech announces the active tab and exposes only the visible panel.
  • Notes — the active-tab underline is a decorative ::after and conveys nothing on its own (aria-selected carries the state); under forced-colors: active it is re-painted as the system Highlight color so the selected tab stays distinguishable in Windows High Contrast. The cancelable re-change event lets you veto a tab switch without breaking these semantics. See the accessibility guide for the project-wide stance.