The floating table of contents

Building the TOC pill

3 min read

At the end of June I saw a table of contents interaction by Raphael Salaja on Twitter, and I loved it. I had a TOC on my site before as a floating sidebar and it took a significant width but Raphael’s design was beautiful as it asked very little. A long page, and a small quiet object at the bottom keeping track of where the user is in it.

While the version I saw on Twitter was super cool, it wouldn’t fit what I needed. So I built my own version, and it has been sitting under the notes and essays here since July. The honest measure of whether it worked is that I stopped noticing it. That’s the best outcome for any interaction, it’s not there unless you want it and it invites your notice with a slight rolling animation.

It’s fixed at the bottom center of the page, collapsed down to the name of whatever section you’re currently reading. Tap it and it opens upward into the full list of sections. Tapping on a section scrolls the site to that position and then the pill collapses itself.

Opening Notes
The Problem
A Small Detour
What Changed
Numbers, Roughly
Where This Goes
Playing by itself Step 1 of 3
Scroll the page. The label rolls to the section you're in.
Scroll inside the frame — the label rolls and the rail fills as you pass each section. Tap the pill to open the list.

Finding the perfect spot

The scroll-spy is the bit I rebuilt the most. The obvious way to decide which heading is “current” is to wait for the previous one to scroll off the top of the screen. It’s simple, but it felt broken. You read a new section for a full paragraph while the label still insists you’re in the last one.

So instead of a boundary at the top, there’s a thin band roughly a quarter to a third of the way down the viewport, and a heading becomes current the moment it crosses into that band. That’s near where your eyes actually are. This approach took the lag in updating the current section away completely, and it still feels natural. (The demos below run a slightly higher band than the real one, because their frames are shorter than a viewport. Reading position is relative to what you’re reading in.)

The label itself rolls like an odometer rather than swapping, because I wanted you to notice it change. A plain swap would be too subtle. The new section enters from the direction you were scrolling and the old one leaves the opposite way, so scrolling down pushes text up, and scrolling back up pulls it back down.

Where It Starts
The Band
Crossing Over
Reading Position
Why It Lags Otherwise
Playing by itself Step 1 of 1
Scroll the page. The label rolls to the section you're in.
The shaded strip is the band. A section goes current the moment its heading crosses into it, not when the one before it leaves the top.

The expanded list

Tap the pill and it opens into the full list: the sections you’ve passed and the ones still ahead.

First Movement
Second Movement
Third Movement
Coda
Playing by itself Step 1 of 1
Scroll the page. The label rolls to the section you're in.
Left open so the rail is visible: a solid dot behind you, a pulsing one where you are, faint rings ahead.

The Caveat

All the motion is gated on prefers-reduced-motion. If you’ve asked your system to calm down, the label swaps instead of rolling, which could potentially make the pill less visible than it should be.

plant maturity Plant — this piece is growing into shape