Skip to content

<u-tabs>

<u-tabs> is not a native HTML element, but follows ARIA authoring practices for tabs. It lets you navigate between groups of information that appear in the same context.

Quick intro:

  • Use <u-tabs> to group all tabbing-elements
  • Use <u-tablist> around multiple <u-tab> direct children - these are the labels
  • Use <u-tab aria-selected="true"> to set the open tab (defaults to first tab)
  • Use <u-tab aria-controls="id-of-panel"> if you need panels outside <u-tabs>
  • Use <u-tabpanel>s after <u-tablist> - these hide/show content of related <u-tab>
  • ARIA Authoring Practices Guide Docs: Tabs

Example

Install

bash
npm add -S @u-elements/u-tabs
bash
pnpm add -S @u-elements/u-tabs
bash
yarn add @u-elements/u-tabs
bash
bun add -S @u-elements/u-tabs
html
<script type="module" src="https://unpkg.com/@u-elements/u-tabs@latest/dist/u-tabs.js"></script>

Attributes and props

<u-tabs>

  • Attributes: all global HTML attributes such as id, class, data-
  • DOM interface: UHTMLTabsElement extends HTMLElement
    • UHTMLTabsElement.tabList returns the contained UHTMLTabListElement
    • UHTMLTabsElement.selectedIndex sets or gets a number reflecting the index of the first selected <u-tab> element. Will ignore invalid indexes.
    • UHTMLTabsElement.tabs returns a NodeListOf<UHTMLTabElement>
    • UHTMLTabsElement.panels returns a NodeListOf<UHTMLTabPanelElement>

<u-tablist>

  • Attributes: all global HTML attributes such as id, class, data-
  • DOM interface: UHTMLTabListElement extends HTMLElement
    • UHTMLTabListElement.tabsElement returns the parent UHTMLTabsElement
    • UHTMLTabsElement.selectedIndex sets or gets a number reflecting the index of the first selected <u-tab> element. Will ignore invalid indexes.
    • UHTMLTabsElement.tabs returns a NodeListOf<UHTMLTabElement>

<u-tab>

  • Attributes: all global HTML attributes such as id, class, data-
    • aria-selected can contain "true" or "false" to set the currently selected tab
    • aria-controls can contain ID the <u-tabpanel> to control
  • DOM interface: UHTMLTabElement extends HTMLElement
    • UHTMLTabElement.tabsElement returns the parent UHTMLTabsElement
    • UHTMLTabElement.selected sets or gets true or false, indicating whether this tab is currently selected
    • UHTMLTabElement.index returns a number representing the position/index within the list of tabs
    • UHTMLTabElement.panel returns the associated UHTMLTabPanelElement

<u-tabpanel>

  • Attributes: all global HTML attributes such as id, class, data-
  • DOM interface: UHTMLTabPanelElement extends HTMLElement
    • UHTMLTabPanelElement.tabsElement returns the parent UHTMLTabsElement
    • UHTMLTabPanelElement.tabs returns a associated NodeListOf<UHTMLTabElement>

Events

No custom events beyond the usual events supported by HTML elements. Tabbing triggers a click (both with mouse and keyboard) so listen for this to check for tab change:

js
document.addEventListener('click', ({ target }) => {
  const tab = target instanceof Element && target.closest('u-tab');

  if (tab) {
    console.log('changed to tab:', tab);
  }
})

Styling

<u-tabs>, <u-tablist> and <u-tabpanel> renders as display: block, while <u-tab> renders as display: inline-block.

Styling the tab state

<u-tab> automatically gets a aria-selected="true" or aria-selected="false" attribute, which can be utilized for styling:

css
.my-tab[aria-selected="true"] {
  /* Your active tab styling here */
}
.my-tab[aria-selected="false"] {
  /* Your inactive tab styling here */
}

Styling example: Scrolling tablist

Styling example: Wrapping tablist

Server side rendering

You can server side render <u-tabs> by using Declarative Shadow DOM. Example:

TS
import { UHTMLTabsShadowRoot, UHTMLTabListShadowRoot, UHTMLTabShadowRoot, UHTMLTabPanelShadowRoot } from '@u-elements/u-tabs';

const renderToStaticMarkup = (tabs: string[], panels[]) =>
 `<u-tabs>
   ${UHTMLTabsShadowRoot}
   <u-tablist>
     ${UHTMLTabListShadowRoot}
     ${tabs.map((tab: string) =>
       `<u-tab>${UHTMLTabShadowRoot}${tab}</u-tab>`
     ).join('')}
   </u-tablist>
   ${panels.map((panel: string) =>
     `<u-tabpanel>${UHTMLTabPanelShadowRoot}${panel}</u-tabpanel>`
   ).join('')}
 </u-tabs>`

Accessibility

Screen reader <u-tabs>
VoiceOver (Mac) + Chrome
VoiceOver (Mac) + Edge
VoiceOver (Mac) + Firefox
VoiceOver (Mac) + Safari
VoiceOver (iOS) + Safari
Jaws (PC) + Chrome
Jaws (PC) + Edge
Jaws (PC) + Firefox
NVDA (PC) + Chrome
NVDA (PC) + Edge
NVDA (PC) + Firefox
Narrator (PC) + Chrome
Narrator (PC) + Edge
Narrator (PC) + Firefox
TalkBack (Android) + Chrome
TalkBack (Android) + Firefox
TalkBack (Android) + Samsung Internet

Specifications

Changelog

  • 0.0.12: Respect aria-disabled="true"
  • 0.0.11: Enable declarative shadow root support and export UHTMLTabsShadowRoot, UHTMLTabListShadowRoot, UHTMLTabShadowRoot and UHTMLTabPanelShadowRoot for easier server side rendering
  • 0.0.10: Automatically adds aria-hidden="true" when hidden to prevent Safari 18.6 from announcing hidden panels

Released under the MIT License