Tabs

Tabs

Material Design 3 tabs, from the HTML.

A <nav class="tabs"> of <a href="#panel"> is the bar. A <span> (or the link text) is the label; a leading <span class="material-symbols" aria-hidden="true"> is the icon. Nest a <span class="badge"> in the icon for a count, and put that count in the tab's aria-label so a hidden icon does not swallow it. .active is the selected tab. There is no li.tab required — ul.tabs > li.tab > a stays as an alias. AutoInit() starts every .tabs except those marked no-autoinit.

Tokens follow the M3 tabs spec. Primary tabs sit on surface, 48dp (64dp with a stacked icon). The label is title-small / on-surface-variant; selected is primary. The indicator is 3dp primary with 3dp top corners. The icon is 24dp. A 1dp outline-variant divider runs under the bar. Hover is 8%; focus is 10%. Disabled is 38%.

Flight
Luggage
Explore
<nav class="tabs" aria-label="Travel">
  <a href="#flight">
    <span class="material-symbols" aria-hidden="true">flight</span>
    <span>Flight</span>
  </a>
  <a class="active" aria-current="page" href="#luggage" aria-label="Luggage, 3 unread">
    <span class="material-symbols" aria-hidden="true">luggage<span class="badge">3</span></span>
    <span>Luggage</span>
  </a>
  <a href="#explore">
    <span class="material-symbols" aria-hidden="true">explore</span>
    <span>Explore</span>
  </a>
</nav>
<div id="flight">Flight</div>
<div id="luggage">Luggage</div>
<div id="explore">Explore</div>

Add disabled on the <a> (or on a wrapping li.tab) to make it inaccessible. Tabs become scrollable when there are too many to fit.

Test 1
Test 2
Test 3
Test 4
Test 5
Test 6
Test 7
Test 8
Test 9
Test 10
Test 11

Secondary

tabs-secondary is the in-content variant: always 48dp, icons inline, a 2dp indicator. Use it to further split a pane.

Travel
Hotel
Walks
<nav class="tabs tabs-secondary" aria-label="Trip">
  <a href="#travel">
    <span class="material-symbols" aria-hidden="true">flight</span>
    <span>Travel</span>
  </a>
  <a class="active" aria-current="page" href="#hotel">
    <span class="material-symbols" aria-hidden="true">hotel</span>
    <span>Hotel</span>
  </a>
</nav>

Initialization

The IIFE bundle exposes Expressive.Tabs. Call init yourself when you need options other than the defaults, or let Expressive.AutoInit() start every .tabs.

document.addEventListener('DOMContentLoaded', function() {
  const elems = document.querySelectorAll('.tabs');
  const instances = Expressive.Tabs.init(elems, {
    duration: 300
  });
});

Options

Name Type Default Description
duration Number 300 Indicator transition duration, in milliseconds.
onShow Function null Called when a new tab panel is shown. Receives the panel element.
swipeable Boolean false Enable swipeable tabs. Uses responsiveThreshold. Wraps the panels in a carousel.
responsiveThreshold Number Infinity Maximum viewport width, in pixels, at which swipeable mode starts. Wider viewports stay non-swipeable.

Methods

All methods are called on the plugin instance. You can get the instance like this:
const instance = Expressive.Tabs.getInstance(elem);
.select();

Show the panel that belongs to the tab with this id.

String: The id of the tab panel (without #).

instance.select('tab_id');
.updateTabIndicator();

Recalculate the indicator position. Useful if the bar was hidden or resized.

instance.updateTabIndicator();
.destroy();

Destroy the plugin instance and tear down its event handlers.

instance.destroy();

Properties

Name Type Description
el Element The DOM element the plugin was initialized with (the .tabs bar).
options Object The options the instance was initialized with.
index Number Index of the tab that is currently shown.

Preselecting a tab

The first tab is selected by default. To pick another, add active to its <a>, or load the page with a matching hash such as #test2.

<a class="active" href="#test3">Test 3</a>

Linking to an External Page

Tabs ignore default anchor behaviour. To keep a tab as a normal hyperlink, set target on the link.

<nav class="tabs" aria-label="Sections">
  <a target="_blank" href="https://github.com">External link in new window</a>
  <a target="_self" href="https://github.com">External link in same window</a>
</nav>

Swipeable Tabs

Set swipeable: true to swipe between panels on touch devices. Keep the tab panels in the same wrapping container. The implementation wraps those panels in a carousel. responsiveThreshold is the viewport width below which swipeable mode turns on.

This demo is marked no-autoinit and started with swipeable: true.

Test 1
Test 2
Test 3
<nav id="tabs-swipe-demo" class="tabs" aria-label="Sections">
  <a href="#test-swipe-1">Test 1</a>
  <a class="active" aria-current="page" href="#test-swipe-2">Test 2</a>
  <a href="#test-swipe-3">Test 3</a>
</nav>
Expressive.Tabs.init(document.querySelector('#tabs-swipe-demo'), {
  swipeable: true
});

Fixed width

Add max so every tab grows equally. On compact viewports every tab bar already uses this layout.

Test 1
Test 2
Test 3
Test 4
Test 5
<nav class="tabs max" aria-label="Sections">
  <a href="#test1">Test 1</a>
  <a class="active" aria-current="page" href="#test2">Test 2</a>
  <a class="disabled" href="#test3">Disabled</a>
</nav>

Inline icons

Primary tabs stack the icon above the label (64dp). Add horizontal (or tabs-horizontal) to put them on one line, like secondary tabs.

Flight
Luggage
Explore
<nav class="tabs max horizontal" aria-label="Sections">
  <a href="#flight">
    <span class="material-symbols" aria-hidden="true">flight</span>
    <span>Flight</span>
  </a>
  <a class="active" aria-current="page" href="#luggage">
    <span class="material-symbols" aria-hidden="true">luggage</span>
    <span>Luggage</span>
  </a>
</nav>
  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.