# Tabs

> Material Design 3 tabs, from the HTML.

## Introduction

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](https://m3.material.io/components/tabs/specs). 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](#tabs-intro-flight) [Luggage](#tabs-intro-luggage) [Explore](#tabs-intro-explore)

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](#tabs-scroll-1) [Test 2](#tabs-scroll-2) [Test 3](#tabs-scroll-3) [Test 4](#tabs-scroll-4) [Test 5](#tabs-scroll-5) [Disabled](#tabs-scroll-6) [Test 7](#tabs-scroll-7) [Test 8](#tabs-scroll-8) [Test 9](#tabs-scroll-9) [Disabled](#tabs-scroll-10) [Test 11](#tabs-scroll-11)

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](#tabs-sec-birds) [Hotel](#tabs-sec-cats) [Walks](#tabs-sec-dogs)

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](#tabs-swipe-1) [Test 2](#tabs-swipe-2) [Test 3](#tabs-swipe-3)

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](#tabs-fixed-1) [Test 2](#tabs-fixed-2) [Disabled](#tabs-fixed-3) [Test 4](#tabs-fixed-4) [Test 5](#tabs-fixed-5)

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](#tabs-horiz-flight) [Luggage](#tabs-horiz-luggage) [Explore](#tabs-horiz-explore)

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>
```
