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.
Basic example
Section titled “Basic example”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.