Skip to content

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.

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.

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.

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.

StateHow it is setStyling
Active sectioninstallSpy 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-visible2px 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.

  • Label the <nav> (aria-label="On this page" or aria-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-ring to match the rest of the system.

Component tokens (in component.tokens.json):

Token pathPurpose
toc.font-sizeText size for the whole list.
toc.gapVertical space between links.
toc.indentInline-start padding (room for the active marker).
toc.link.fg / hover-fgInactive link color, rest and hover.
toc.active.fg / font-weightActive link appearance.
toc.active.markerColor of the inline-start marker on the active link.
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)
  • Breadcrumb — trail from the site root to the current page.
  • Tabs — switch between sibling sections.