Table of contents
hc-toc is a pure-CSS skin over a labeled <nav> wrapping a list of
in-page anchor links — the familiar “On this page” jump list for long
reference pages. It works with no JavaScript. Add data-hc-spy and the
installSpy behavior marks the link of the
section currently in view as the reader scrolls.
Also known as: table of contents, on-this-page navigation.
Basic HTML
Section titled “Basic HTML”The active link below is marked statically with aria-current="location"
to show the styling; with data-hc-spy the behavior sets it for you.
<nav class="hc-toc" aria-label="On this page"> <ul class="hc-toc__list"> <li class="hc-toc__item"><a class="hc-toc__link" href="#sec-inputs">Inputs</a></li> <li class="hc-toc__item"><a class="hc-toc__link" href="#sec-sql">SQL</a></li> <li class="hc-toc__item"><a class="hc-toc__link" href="#sec-tests">Tests</a></li> </ul></nav>The <li class="hc-toc__item"> wrappers are optional — the styling keys
off .hc-toc__list / .hc-toc__link, so a flat list of links directly
inside the nav works too.
Scrollspy (data-hc-spy)
Section titled “Scrollspy (data-hc-spy)”Add data-hc-spy to the nav and install the behavior. It resolves each
link’s #fragment to its target section, observes them with an
IntersectionObserver, and marks the link of the section at the top of
the viewport with aria-current="location" plus a data-active hook.
<nav class="hc-toc" data-hc-spy aria-label="On this page"> <a class="hc-toc__link" href="#sec-inputs">Inputs</a> <a class="hc-toc__link" href="#sec-sql">SQL</a> <a class="hc-toc__link" href="#sec-tests">Tests</a></nav>…<section id="sec-inputs">…</section><section id="sec-sql">…</section><section id="sec-tests">…</section>The behavior only tracks sections that exist, and resolves links once at
install (re-install after swapping the tracked content). It never forces
smooth scrolling — clicking a link is the browser’s native anchor jump —
so there is nothing for prefers-reduced-motion to gate.
States
Section titled “States”| State | How it is set | Styling |
|---|---|---|
| Active section | installSpy sets aria-current="location" + data-active (or author it directly) | --hc-toc-active-fg, --hc-toc-active-font-weight, and the inline-start marker (--hc-toc-active-marker) |
| Hover | :hover | --hc-toc-link-fg → --hc-toc-link-hover-fg |
| Focus | :focus-visible | 2px outline in --hc-color-focus-ring |
The active rule matches both [aria-current="location"] and
[data-active], so the highlight works whether it comes from the
behavior or from server-rendered markup.
Accessibility
Section titled “Accessibility”- Label the
<nav>(aria-label="On this page"oraria-labelledby) so the landmark has a name. - The active link carries
aria-current="location"— the ARIA value for “the current location within a set” — which drives both the visual state and the screen-reader signal. - Without JavaScript the nav is still a working list of anchor links (no active highlight) — progressive enhancement.
- Keep the focus outline; the default uses
--hc-color-focus-ringto match the rest of the system.
Theming tokens
Section titled “Theming tokens”Component tokens (in component.tokens.json):
| Token path | Purpose |
|---|---|
toc.font-size | Text size for the whole list. |
toc.gap | Vertical space between links. |
toc.indent | Inline-start padding (room for the active marker). |
toc.link.fg / hover-fg | Inactive link color, rest and hover. |
toc.active.fg / font-weight | Active link appearance. |
toc.active.marker | Color of the inline-start marker on the active link. |
CSS variables
Section titled “CSS variables”Show the generated CSS variables
--hc-toc-font-size | -gap | -indent--hc-toc-link-fg | -link-hover-fg--hc-toc-active-fg | -active-font-weight | -active-marker--hc-color-focus-ring (inherited from data-color)Related
Section titled “Related”- Breadcrumb — trail from the site root to the current page.
- Tabs — switch between sibling sections.