The A–Z index is the phone-book browsing pattern: a rail of letter squares that jump to letter groups further down the page. Letters with no entries stay in the rail as disabled squares, so the alphabet never reflows as filters change. The rail is sticky, docking under the site header (and the page bar, where one is docked) while the list scrolls beneath it.
The rail's contents are generated by assets/js/az-index.js from the letter groups actually present — it is never authored by hand. It was ported from the standalone glossary site and consumes it here on the Glossary page.
Anatomy
nav.az-index[data-az-index="<list-id>"] ← the rail; letters generated by JS
a.az-index-letter ← a letter with entries, links to its group
span.az-index-letter.is-disabled ← a letter without entries, held in place
#<list-id> ← the list the rail indexes
section.az-group[id][data-letter] ← one letter group, the jump target
Usage
Author the rail empty and the groups with id and data-letter; the JS fills the rail in.
<nav class="az-index" data-az-index="entry-list" aria-label="Jump to letter"></nav>
<div id="entry-list">
<section class="az-group" id="letter-a" data-letter="A">…</section>
<section class="az-group" id="letter-c" data-letter="C">…</section>
</div>
Then build the rail once the groups are in the DOM, and again whenever filtering changes which groups are visible:
window.azIndex.build(document.querySelector('[data-az-index]'));
build() skips groups carrying , so a filtered-away letter goes disabled on the next build. Call window.azIndex.reset() when the rail's page is torn down by a client-side navigation — it disconnects the module's observers and listeners.
Entries whose name starts with a digit or symbol group under #, shown at the end of the rail.
Sticky behaviour
The rail docks at --scroll-offset minus its breathing-room term, so it lands directly under whatever the page already docks — header alone, or header plus a sticky bar. It publishes its own measured height to --az-index-height on :root, kept current as the alphabet rewraps across widths; jump targets read that variable in their scroll-margin-top so a clicked letter never lands its heading under the rail.
One structural requirement: no ancestor between the rail and the page scroller may create a scroll container. overflow: hidden on an ancestor silently disables position: sticky — uses overflow: clip for exactly this reason.
Accessibility
- Letters with entries are real
<a href="#…">links — they work without JS and announce as links to the sections they reach. - Letters without entries are
<span aria-hidden="true">, not focusable, kept visually so the alphabet does not shift under the cursor. - The letter whose group is being read is marked
aria-current="true"as the page scrolls (IntersectionObserver). - The rail is a
<nav>; give it anaria-labelnaming what it jumps through. - Smooth scrolling to a letter is guarded by
prefers-reduced-motionat the page level — readers who asked for reduced motion get an instant jump.
When to use
- A long alphabetical list where scanning beats scrolling — a glossary, an index, a directory
When not to use
- Short lists a single screen can hold; the rail costs more attention than it saves
- Lists with a meaningful non-alphabetical order — the rail argues for A–Z and loses
CSS reference
This section documents how the component is built. For usage, see the sections above.
Tokens
| Token | Default | What it controls |
|---|---|---|
3rem |
The rail's measured height, published by the JS; jump targets add it to their scroll margin. The CSS value is only the pre-measurement placeholder | |
| – | Where the rail docks (docs-site shell) | |
| / | – | Letter square fill, rest and hover |
| / | – | Letter colour, rest and hover; inverted pair for the active state |
| – | Disabled letter colour | |
| – | Letter square corner | |
| – | Focus outline colour | |
| / | – | Hover transition |
Selectors
| Selector | Purpose |
|---|---|
| The rail: wrapping flex row, sticky under the docs chrome, opaque background | |
| One letter square; fixed minimum size so active-state changes never reflow the row | |
.az-index-letter.is-active |
The letter currently being read — inverted colours, aria-current |
.az-index-letter.is-disabled |
A letter with no entries — faded, transparent, unclickable |
.az-group[id] |
A letter group; carries the scroll margin that clears header, bar, and rail |