<!-- Generated by tools/docs.php from docs/src/nav.html - edit the source, not this file. -->

# Navs and tabs

A row of links between views of one thing - `nav-underline` - or of choices - `nav-pills`; and the same row as tabs that switch panels on the page, driven by the keyboard as the WAI-ARIA tabs pattern.

## Examples

Links between pages are a `<nav>`; the current one is `aria-current="page"`.

```html
<nav class="nav nav-underline" aria-label="List views">
	<a class="nav-link" href="#nav-links" aria-current="page">All<span class="badge">28</span></a>
	<a class="nav-link" href="#nav-links">Needs attention<span class="badge badge-subtle-warning">3</span></a>
	<a class="nav-link" href="#nav-links">Drafts</a>
</nav>
<nav class="nav nav-pills" aria-label="Period">
	<a class="nav-link" href="#nav-links" aria-current="page">Week</a>
	<a class="nav-link" href="#nav-links">Month</a>
	<a class="nav-link" href="#nav-links">Year</a>
</nav>
```

### Tabs

Tabs switch panels in place: `role="tablist"`, each tab `role="tab"` with `aria-selected` and `aria-controls`, each panel `role="tabpanel"`. The library keeps only the selected tab in the tab order and only its panel shown. The arrow keys select as they move.

```html
<div class="nav nav-underline" role="tablist" aria-label="Excursion">
	<button class="nav-link" type="button" role="tab" id="nav-tabs-1" aria-selected="true" aria-controls="nav-tabs-panel-1">Details</button>
	<button class="nav-link" type="button" role="tab" id="nav-tabs-2" aria-selected="false" aria-controls="nav-tabs-panel-2">Prices</button>
	<button class="nav-link" type="button" role="tab" id="nav-tabs-3" aria-selected="false" aria-controls="nav-tabs-panel-3">History</button>
</div>
<div id="nav-tabs-panel-1" role="tabpanel" aria-labelledby="nav-tabs-1" tabindex="0"><p>A day at sea: snorkelling at two reefs, lunch on the boat.</p></div>
<div id="nav-tabs-panel-2" role="tabpanel" aria-labelledby="nav-tabs-2" tabindex="0" hidden><p>Adults 2,500 · children 1,900 · under four free.</p></div>
<div id="nav-tabs-panel-3" role="tabpanel" aria-labelledby="nav-tabs-3" tabindex="0" hidden><p>Edited yesterday by M. K.; published on three sites.</p></div>
```

### A tab with changes

`data-changed`, set by the page, puts a dot on a tab whose panel holds something not saved yet, the same word as on a field. Its `aria-label` says so to a screen reader.

```html
<div class="nav nav-underline" role="tablist" aria-label="Settings">
	<button class="nav-link" type="button" role="tab" id="nav-changed-1" aria-selected="true" aria-controls="nav-changed-panel-1">Company</button>
	<button class="nav-link" type="button" role="tab" id="nav-changed-2" aria-selected="false" aria-controls="nav-changed-panel-2" aria-label="Mail, not saved" data-changed>Mail</button>
</div>
<div id="nav-changed-panel-1" role="tabpanel" aria-labelledby="nav-changed-1" tabindex="0"><p>The company's name and address.</p></div>
<div id="nav-changed-panel-2" role="tabpanel" aria-labelledby="nav-changed-2" tabindex="0" hidden><p>The address mail is sent from.</p></div>
```

### Tabs whose panels load

When a panel comes from the server, the arrows should not load every panel they pass: `data-activation="manual"` on the tablist makes them move the focus only, and `Enter` or `Space` selects.

```html
<div class="nav nav-pills" role="tablist" aria-label="Report" data-activation="manual">
	<button class="nav-link" type="button" role="tab" id="nav-manual-1" aria-selected="true" aria-controls="nav-manual-panel-1">Bookings</button>
	<button class="nav-link" type="button" role="tab" id="nav-manual-2" aria-selected="false" aria-controls="nav-manual-panel-2">Revenue</button>
	<button class="nav-link" type="button" role="tab" id="nav-manual-3" aria-selected="false" aria-controls="nav-manual-panel-3">Refunds</button>
</div>
<div id="nav-manual-panel-1" role="tabpanel" aria-labelledby="nav-manual-1" tabindex="0"><p>128 bookings this week.</p></div>
<div id="nav-manual-panel-2" role="tabpanel" aria-labelledby="nav-manual-2" tabindex="0" hidden><p>312,400 THB this week.</p></div>
<div id="nav-manual-panel-3" role="tabpanel" aria-labelledby="nav-manual-3" tabindex="0" hidden><p>2 refunds this week.</p></div>
```

### A tab that asks before it is left

Before a person's click or key selects a tab, the tablist fires the cancelable `redz:select`; its `detail` holds `tab`, the one asked for, and `from`, the one being left. A page that must ask first - a form with unsaved edits - calls `event.preventDefault()` and `event.detail.respondWith(promise)`: the tab is selected when the promise gives `true`, and otherwise the one being left stays, with the focus. Change the note below, then choose another tab.

```html
<div class="nav nav-underline" role="tablist" aria-label="Excursion">
	<button class="nav-link" type="button" role="tab" id="nav-guard-1" aria-selected="true" aria-controls="nav-guard-panel-1">Note</button>
	<button class="nav-link" type="button" role="tab" id="nav-guard-2" aria-selected="false" aria-controls="nav-guard-panel-2">Prices</button>
</div>
<div id="nav-guard-panel-1" role="tabpanel" aria-labelledby="nav-guard-1" tabindex="0">
	<label class="form-label" for="nav-guard-note">A note for the guides</label>
	<input class="form-control" id="nav-guard-note" type="text" value="Meet at the pier at 8:00">
</div>
<div id="nav-guard-panel-2" role="tabpanel" aria-labelledby="nav-guard-2" tabindex="0" hidden><p>2,900 THB an adult.</p></div>
```

```js
tablist.addEventListener('redz:select', (event) => {
  if (form.dataset.changed !== undefined) {
    event.preventDefault();
    event.detail.respondWith(REDZ.ui.confirm({title: 'Leave without saving?', ok: 'Leave'}));
  }
});
```

### Tabs in the address

A tablist with `data-hash` follows the address: `location.hash` naming a panel, or anything inside one, selects its tab - when the page loads and when the hash changes, a tablist inside a panel too -, and a person's choice writes the panel's id into the address, so a link or a reload opens the same tab. After every selection, the first one too, the tablist fires `redz:selected` with `detail.tab`. `REDZ.ui.select(tab)` chooses a tab from code as a person would - through `redz:select`, so the page can still refuse - and answers a promise of whether it was selected.

```html
<div class="nav nav-underline" role="tablist" aria-label="Booking" data-hash>
	<button class="nav-link" type="button" role="tab" id="nav-hash-1" aria-selected="true" aria-controls="nav-hash-guests">Guests</button>
	<button class="nav-link" type="button" role="tab" id="nav-hash-2" aria-selected="false" aria-controls="nav-hash-payment">Payment</button>
	<button class="nav-link" type="button" role="tab" id="nav-hash-3" aria-selected="false" aria-controls="nav-hash-history">History</button>
</div>
<div id="nav-hash-guests" role="tabpanel" aria-labelledby="nav-hash-1" tabindex="0"><p>Two adults, one child.</p></div>
<div id="nav-hash-payment" role="tabpanel" aria-labelledby="nav-hash-2" tabindex="0" hidden><p>Paid in full by card on 2 November.</p></div>
<div id="nav-hash-history" role="tabpanel" aria-labelledby="nav-hash-3" tabindex="0" hidden><p>Booked on 1 November; the date moved once.</p></div>
<p><a href="#nav-hash-payment">The payment</a> opens its tab.</p>
```

```js
tablist.addEventListener('redz:selected', (event) => load(event.detail.tab));
await REDZ.ui.select(document.getElementById('nav-hash-3'));
```

### Filling the width

`nav-fill` lets the links share the width by their length, `nav-justified` in equal parts.

```html
<nav class="nav nav-underline nav-fill" aria-label="Filled">
	<a class="nav-link" href="#nav-fill" aria-current="page">Overview</a>
	<a class="nav-link" href="#nav-fill">Prices and seasons</a>
	<a class="nav-link" href="#nav-fill">Reviews</a>
</nav>
<nav class="nav nav-underline nav-justified" aria-label="Justified">
	<a class="nav-link" href="#nav-fill" aria-current="page">Overview</a>
	<a class="nav-link" href="#nav-fill">Prices and seasons</a>
	<a class="nav-link" href="#nav-fill">Reviews</a>
</nav>
```

### Where you are on the page

A nav or a list group with `data-scrollspy` marks the link to the section in view `aria-current="location"` as the page - or the box the sections scroll in - scrolls: the last section whose top has passed a quarter of the height, or the last one at the end.

```html
<div class="grid md:cols-12 gap-16">
	<nav class="list-group list-group-flush md:span-4" aria-label="On this example" data-scrollspy>
		<a class="list-group-item list-group-item-action" href="#nav-spy-plan">Plan</a>
		<a class="list-group-item list-group-item-action" href="#nav-spy-boat">The boat</a>
		<a class="list-group-item list-group-item-action" href="#nav-spy-lunch">Lunch</a>
		<a class="list-group-item list-group-item-action" href="#nav-spy-back">Back</a>
	</nav>
	<div class="docs-scroll md:span-8 vstack gap-16" tabindex="0" role="region" aria-label="The day, by section">
		<section id="nav-spy-plan" class="vstack gap-4"><h4 class="fs-14">Plan</h4><p class="mb-0">Pick-up at the hotel at 7:30, the pier at 8:15. The crew hands out life vests and snorkels before the boat leaves the bay.</p></section>
		<section id="nav-spy-boat" class="vstack gap-4"><h4 class="fs-14">The boat</h4><p class="mb-0">A speedboat for 30 guests with shade and a toilet; the ride to the park takes an hour across open water.</p></section>
		<section id="nav-spy-lunch" class="vstack gap-4"><h4 class="fs-14">Lunch</h4><p class="mb-0">Lunch on a beach of the park: rice, green curry, fruit and water; vegetarian on request.</p></section>
		<section id="nav-spy-back" class="vstack gap-4"><h4 class="fs-14">Back</h4><p class="mb-0">The boat leaves the park at 15:30 and reaches the pier by 17:00; the transfer brings everyone back to the hotel.</p></section>
	</div>
</div>
```

### A row that scrolls

A nav or a tab list wider than its box scrolls sideways, and the edge it goes on past fades - `data-more` says which, `start`, `end` or both, as the person scrolls; when the page loads, the current or selected item is scrolled into the row's view, the page itself left where it is.

```html
<nav class="nav nav-underline max-w-320" aria-label="Task views">
	<a class="nav-link" href="#nav-scrolling">All <span class="badge">48</span></a>
	<a class="nav-link" href="#nav-scrolling">New <span class="badge">6</span></a>
	<a class="nav-link" href="#nav-scrolling">Doing <span class="badge">9</span></a>
	<a class="nav-link" href="#nav-scrolling">Checking <span class="badge">4</span></a>
	<a class="nav-link" href="#nav-scrolling" aria-current="page">Done <span class="badge">25</span></a>
	<a class="nav-link" href="#nav-scrolling">Returned <span class="badge">3</span></a>
	<a class="nav-link" href="#nav-scrolling">Cancelled <span class="badge">1</span></a>
</nav>
```

### When the links do not fit

`nav-overflow` around a nav keeps it on one line: the links that do not fit hide from the end and come back, in their order, as the items of a menu behind a “More” button after the nav. The link the person is on and a link with `nav-overflow-keep` stay in the row. A link that opens a [menu](menu.md) folds with it, as a group of its items under the link's words, and stays in the row while its menu holds the page the person is on; a [navbar](navbar.md) shows one. A link the page hides with `hidden` leaves the row and the menu, and comes back when the page shows it. It measures the room its own box has, not the window, so a nav in a narrow column folds as early as it needs to. For a row of links the wrapper is the `<nav>`, so the menu stays inside the landmark.

```html
<nav class="nav-overflow max-w-480" aria-label="Excursion sections">
	<div class="nav nav-underline">
		<a class="nav-link" href="#nav-overflow" aria-current="page">Overview</a>
		<a class="nav-link" href="#nav-overflow">Program</a>
		<a class="nav-link" href="#nav-overflow">Prices</a>
		<a class="nav-link" href="#nav-overflow">Dates</a>
		<a class="nav-link" href="#nav-overflow">Pickup</a>
		<a class="nav-link" href="#nav-overflow">Reviews<span class="badge">42</span></a>
		<a class="nav-link" href="#nav-overflow">Photos</a>
		<a class="nav-link" href="#nav-overflow">Questions</a>
	</div>
</nav>
```

Tabs fold the same way. The arrows move between the tabs in the row; a tab chosen in the menu comes back into the row selected, with the focus, and the last one in the row takes its place in the menu.

```html
<div class="nav-overflow max-w-320">
	<div class="nav nav-pills" role="tablist" aria-label="Guest">
		<button class="nav-link nav-overflow-keep" type="button" role="tab" id="nav-overflow-tabs-1" aria-selected="true" aria-controls="nav-overflow-tabs-panel-1">Profile</button>
		<button class="nav-link" type="button" role="tab" id="nav-overflow-tabs-2" aria-selected="false" aria-controls="nav-overflow-tabs-panel-2">Bookings</button>
		<button class="nav-link" type="button" role="tab" id="nav-overflow-tabs-3" aria-selected="false" aria-controls="nav-overflow-tabs-panel-3">Payments</button>
		<button class="nav-link" type="button" role="tab" id="nav-overflow-tabs-4" aria-selected="false" aria-controls="nav-overflow-tabs-panel-4">Messages</button>
		<button class="nav-link" type="button" role="tab" id="nav-overflow-tabs-5" aria-selected="false" aria-controls="nav-overflow-tabs-panel-5">Notes</button>
	</div>
</div>
<div id="nav-overflow-tabs-panel-1" role="tabpanel" aria-labelledby="nav-overflow-tabs-1" tabindex="0"><p>Anna Sample, two trips this year.</p></div>
<div id="nav-overflow-tabs-panel-2" role="tabpanel" aria-labelledby="nav-overflow-tabs-2" tabindex="0" hidden><p>North Bay on 12 December, 2 adults.</p></div>
<div id="nav-overflow-tabs-panel-3" role="tabpanel" aria-labelledby="nav-overflow-tabs-3" tabindex="0" hidden><p>Paid in full by card.</p></div>
<div id="nav-overflow-tabs-panel-4" role="tabpanel" aria-labelledby="nav-overflow-tabs-4" tabindex="0" hidden><p>No messages yet.</p></div>
<div id="nav-overflow-tabs-panel-5" role="tabpanel" aria-labelledby="nav-overflow-tabs-5" tabindex="0" hidden><p>Vegetarian lunch.</p></div>
```

## States

## Keyboard and accessibility

| Key | In a tablist |
| --- | --- |
| `→` / `←` | The next / the previous tab, round the ends - mirrored right to left; `↓` / `↑` in a list with `aria-orientation="vertical"` |
| `Home` / `End` | The first / the last tab |
| `Enter`, `Space` | Selects the focused tab - what the arrows do not, with `data-activation="manual"` |
| `Tab` | From the selected tab into its panel, or first to the “More” button when the row has one |

- Links that change the page are a `<nav>` of links, never tabs: tabs only switch what is already on the page.
- A link folded into the “More” menu is hidden in the row, so a screen reader and `Tab` meet it once, in the menu. The button says the page's text `more`, reports whether its menu is open, and opens it by the menu's keys; a plain click on an item clicks the hidden link itself, so the page's own handlers run.
- A mark beside a tab's words - a dot, a count - has its own name, so the tab reads as “EN, 1 field outdated”.

## Tokens

From [`css/components/nav.css`](https://github.com/redz-tech/REDZ.UI/blob/main/css/components/nav.css). It declares `--redz-fade-left`, `--redz-fade-right` on its root, never on rows or items.

It reads the theme's `--redz-space-16`, `--redz-space-8`, `--redz-text-muted`, `--redz-text`, `--redz-warning`, `--redz-border`, `--redz-space-4`, `--redz-radius-8`, `--redz-bg-hover`, `--redz-space-12`, `--redz-radius-6`, `--redz-fs-12`, `--redz-bg`, `--redz-shadow-1`.

## From Bootstrap 5.3

| Bootstrap 5.3 | REDZ.UI | Change | Note |
| --- | --- | --- | --- |
| `nav-item` | - | remove | links sit straight in the nav |
| `nav-tabs` | `nav-underline` | rename | tabs are drawn as an underline |
| `tab-content` | - | remove | panels are siblings with role="tabpanel", hidden when not selected |
| `tab-pane` | - | remove | panels are siblings with role="tabpanel", hidden when not selected |
| `data-bs-toggle="tab"` | `role="tab" aria-controls="ID"` | trigger | in a role="tablist"; the library selects and shows the panel |
| `data-bs-toggle="pill"` | `role="tab" aria-controls="ID"` | trigger | in a role="tablist" |
| `data-bs-spy` | `data-scrollspy` | trigger | on the nav or list group of the links, not on the scrolled box; the link to the section in view gets aria-current="location" |
| `bootstrap.Tab` | `tab.click()` | review | the library selects on click and by the keys |
| `shown.bs.tab` | - | review | watch aria-selected, or the tab's click |
