Toc
A column of links to the headings on the page, with the one being read marked as the reader scrolls.
Example
View Code
<nav class="toc" aria-label="On this page">
<ul role="list">
<li><a href="#getting-the-files" aria-current="true">Getting the files</a></li>
<li><a href="#the-stylesheet">The stylesheet</a></li>
<li><a href="#a-module">A module</a></li>
<li><a href="#editor-completion">Editor completion</a></li>
</ul>
</nav>
When to use it
A long page a reader scrolls rather than clicks through: a guide, a reference, a post with sections. The list says what is on the page and how far through it the reader is. A page with three short sections does not need one, and neither does a page whose headings would not fit down one side.
How it works
A column of links, each pointing at the id of a heading on the same page. The current one carries aria-current="true" and takes the variant's colour, a strong weight, and a bar down its start edge; the bar is on every link and transparent until then, so nothing shifts as the mark moves. data-variant picks the hue and data-size steps the text.
toc.js moves the mark. It watches the linked headings with one IntersectionObserver and marks the link of whichever heading is topmost in view, dispatching yeti:current on the .toc with the link and the heading each time it moves. Between two headings nothing is in view, and the last mark stays rather than flickering off. Without the module the list is a working list of links, and a page that knows its own current section can simply ship the attribute in its HTML.
Following a link scrolls smoothly, from --yeti-toc-scroll applied to the page — the token collapses to auto under prefers-reduced-motion, which a plain scroll-behavior in this layer could not, since it would outrank the reset's own rule.
<nav class="toc" aria-label="On this page" data-variant="secondary" data-size="sm">
<ul role="list">
<li><a href="#install">Install</a></li>
<li><a href="#usage">Usage</a></li>
</ul>
</nav>
Accessibility
Label the nav — aria-label="On this page" — because a page with a toc has at least two navs and they have to be told apart. Put role="list" on the ul, which is what keeps it a list where the reset removes the markers. The current section's link carries aria-current="true"; the colour and the bar are the visible half of the same fact, never the only half. The module owns that attribute on these links: it sets and clears aria-current="true" and strips any other aria-current value an author puts on a toc link. The links are ordinary same-page links, so the keyboard reaches them in order and Enter follows one with no script at all.
Attributes
| Attribute | Type | Values | Default | Description |
|---|---|---|---|---|
data-variant |
enum | primary, secondary, success, warning, alert, danger, neutral |
primary |
The hue of the current link and its edge bar. |
data-size |
enum | sm, md, lg |
md |
The text step, and the inset of each link with it. |
Children
> ul: exactly 1. The links, one per li, with role="list".li: at least 1. One entry, holding a link whose href is the id of a heading on this page.
Tokens
| Token | Description |
|---|---|
--yeti-toc-scroll |
How the page scrolls when a link is followed. |
--yeti-color-text-muted |
A link that is not the current one. |
--yeti-color-text |
A link under the pointer. |
--yeti-space-xs |
Gap between links, and their block padding. |
--yeti-space-sm |
The space step when data-size is absent. |
--yeti-text-md |
Text size when data-size is absent. |
--yeti-border-width |
The edge bar is three of them. |
--yeti-weight-strong |
Weight of the current link. |
--yeti-duration-fast |
How long the mark takes to move. |
--yeti-ease |
The curve of that transition. |
--yeti-color-primary |
The default variant's colour, when data-variant is absent. |
--yeti-color-primary-subtle |
The default variant's hover tint. |
--yeti-color-primary-text |
The default variant's text colour. |
Internal tokens (may change between minor versions)
- `--_yeti-variant` - `--_yeti-variant-subtle` - `--_yeti-variant-text` - `--_yeti-size-text` - `--_yeti-size-space`Accessibility
- Required attributes:
aria-labeloraria-labelledby - Give the nav an aria-label such as "On this page", since a page with a toc has at least two navs. Put role="list" on the ul, because the reset only removes list markers where that role says the list is decorative. The link for the section being read carries aria-current="true"; toc.js moves it, and a page that ships the mark in its HTML is correct without the module. The module owns aria-current on these links: it sets and clears the value "true" and strips any other aria-current an author puts on a toc link. Every link points at an id on this page, so the list works with no script at all.
Browser support
- Used without guards: IntersectionObserver, :has(), scroll-behavior
- Behind
@supports: nothing
JavaScript
Optional enhancement: components/toc/toc.js. The component works without it.
Each event bubbles, crosses a shadow boundary, and cannot be cancelled.
| Event | Module | Detail | Description |
|---|---|---|---|
yeti:current |
toc.js |
{ link, heading } |
Dispatched on the .toc when the mark moves to another link. |
Available since 7.0.0.