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%.
<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.
Secondary
tabs-secondary is the in-content variant: always 48dp,
icons inline, a 2dp indicator. Use it to further split a pane.
<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.
<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.
<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.
<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>