In-Page Navigation: Table of Contents Design for Long Pages
Navigation DesignTYPENORMLabs9 minOctober 15, 2026

In-Page Navigation: Table of Contents Design for Long Pages

How to design a table of contents for a long web page: when a page needs one, where it goes, how the active-section highlight should behave, the anchor-link details that break it, and what the mobile version looks like.

In January 2023, English Wikipedia switched its default desktop skin to Vector 2022. The most visible change was where the table of contents lives. For years it had been a boxed list under the lead paragraph, visible once and gone as soon as you scrolled past it. The redesign moved it into a column beside the article that stays in view while you read, with the section you are in highlighted.

A list of headings at the top of a page serves a reader who is deciding whether to read. A list that follows you down the page serves a reader who is already reading and wants to move around.

What a table of contents does on a web page

It does two jobs, and they pull in different directions.

The first is preview. Before committing to a long page, a reader scans the section list to see whether the thing they need is in there and roughly where. This job wants the list early and complete.

The second is movement. A reader halfway through wants to jump back to a definition, skip a section they already know, or find the FAQ. This job wants the list reachable from wherever the reader currently is, and it wants to show where they are now.

A reader holding a book can see how thick it is and how far in they are. A web page reduces both to a scrollbar thumb, so the table of contents has to show the reader the shape of the document as well as its contents.

When a page needs one

Length alone is a weak trigger. A 3,000-word essay that is meant to be read straight through gains little from a section list, and a 1,200-word reference page with eight short sections gains a lot. What matters is whether people arrive looking for one part.

Signs that they do:

  • The page answers several distinct questions, and search sends people to it for any one of them.
  • Readers come back to it. Documentation, policies and guides get revisited for one section at a time.
  • The headings are specific enough to be chosen between. A page with sections called "Background", "Discussion" and "Next steps" has nothing for a reader to choose.

If none of these hold, leave it out. A table of contents on a short narrative page is a second copy of the headings, and it pushes the opening paragraph further down.

Top of page, sticky sidebar, or both

The top-of-page list is the simplest version, and it does the preview job well. It works on every screen width, needs no JavaScript and costs almost nothing to build. Its weakness is that it stops existing after the first scroll. A reader who wants it again has to go back to the top.

The sticky sidebar does the movement job. On a desktop layout with room in the margin, it stays pinned while the article scrolls and can mark the current section. Documentation sites have used this layout for years, often under a label like "On this page" or "Contents". It needs a layout with a spare column, and it can't hold much: a long list, especially one with H3s or entries that wrap, outgrows the viewport and has to scroll inside itself, which is awkward to use.

Many pages ship both: a short list at the top for preview, and the sidebar once the reader scrolls past it. That is a reasonable default for long reference content. The cost is two components to keep in sync, so generate both from the same headings rather than writing either by hand.

A fourth variant is a compact control that sits near the title and expands the list on demand. Wikipedia's current skin can collapse its sidebar into one. It is the usual answer on narrow screens, covered below.

Depth: how many heading levels to list

List the H2s. Add H3s only when the H2s alone don't let a reader find things, which in practice means long pages with a few big sections, each holding several distinct subsections.

Two levels is the practical ceiling. A third level turns the list into an outline. If the H3s do go in, indent them and set them smaller so the H2s still read as the primary list, and consider showing only the H3s of the section the reader is currently in.

Scroll highlighting

The active-section highlight, often called scrollspy, answers "where am I?" by name. The scrollbar only answers it in proportion.

It is also easy to get subtly wrong. The common implementation uses IntersectionObserver to watch each heading and marks whichever one most recently crossed a line near the top of the viewport. That breaks in a few places:

  • Short final sections. If the last section is shorter than the viewport, its heading never reaches the trigger line, so the highlight stays on an earlier entry even after the reader scrolls to the very bottom. Treat "scrolled to the bottom" as activating the last entry.
  • Clicking an entry. The reader clicks the fifth entry, the page scrolls, and on the way down the highlight flickers through entries two, three and four. Set the clicked entry as active immediately and suppress observer updates until the scroll settles.
  • Long sections. In a section much taller than the viewport, the reader may be deep inside it with no heading in view. The highlight should stay on that section, which means the logic has to track the last heading above the line, not the heading currently visible.

Mark the current entry in markup as well as in colour. aria-current="true" on the active link tells assistive technology which entry is current, and it gives you a single attribute to style against.

Anchor links that land in the right place

Every entry is an anchor link to a heading's id. Getting the link to land correctly comes down to a handful of details.

Stable ids. Generate them from the heading text, and don't regenerate them when a heading gets edited, or every shared link to that section breaks. If headings change often, assign ids explicitly.

Sticky headers. A fixed site header covers the top of the viewport, so a heading scrolled to the very top ends up hidden behind it. The fix is one CSS property on the target:

h2[id], h3[id] {
  scroll-margin-top: 5rem; /* height of the sticky header, plus a little air */
}

Motion. scroll-behavior: smooth makes the jump easier to follow, since the reader sees the direction and distance they travelled. Wrap it in a prefers-reduced-motion: no-preference media query, so people who have asked their system for less motion get an instant jump.

Focus. When a fragment link is followed, modern browsers move the starting point for keyboard navigation to the target, so the next Tab press continues from the new section. Test that this holds in your build. Client-side routers that intercept link clicks and scroll with JavaScript often skip it, and a keyboard user who presses Tab after jumping ends up back in the sidebar. If your jumps go through JavaScript, put tabindex="-1" on the headings and focus the target yourself once the scroll finishes, with focus({ preventScroll: true }) so the call doesn't cancel a smooth scroll, the same fix our skip navigation guide recommends for its target.

Keep the URL hash updated when someone clicks an entry, so the address bar always holds a link to the section they are looking at. Updating it continuously while the reader scrolls is a different decision: it fills the browser history with entries, and Back stops doing what people expect. Use history.replaceState if you do it at all.

Writing headings for the list

A table of contents is generated from headings, so its quality is the quality of the headings. Draft the list before the page is finished and read it on its own. If someone who has never seen the page can't tell from the list what each section contains, rewrite the headings.

Labels that fail this test include:

  • Generic section names: "Overview", "Details", "More information".
  • Clever headings that need the paragraph under them to make sense.
  • Several headings that start with the same words, so the list reads as one repeated phrase with different endings.

Questions work well as headings on reference pages, because they match what the reader typed into search. Plain noun phrases work everywhere. Keep each entry short enough to sit on one line of the sidebar, or the list grows long and stops being scannable.

The list also exposes structural problems. If one section is a third of the page and the rest are a paragraph each, the list shows it at a glance, and the page probably needs restructuring more than it needs navigation.

Index and hub pages are the exception to keeping entries short. There, the list links out to separate articles, and a bare title is often not enough to choose by. A magazine table of contents handles this with a one-line description under each title and entries grouped by section. Copy that for a hub page. Inside a single article it makes the list too long to scan.

Table of contents on mobile

A narrow screen has no margin for a sidebar, so the movement job needs another home.

The usual answer is a collapsed control, a bar that sits under the site header showing the current section's name. Tap it and the full list opens over the content. Tap an entry and it closes and jumps. Because the bar shows the current section name even when closed, it keeps the "where am I?" answer that scroll highlighting provides on desktop.

The bar takes vertical space on a screen that has little to spare, so keep it to one line and fold it into the site header if there already is a sticky one. Decide which pattern the opened list follows and build all of it. If the list pushes the content down, it is a disclosure: the toggle carries aria-expanded and focus stays on it. If the list opens over the content, it behaves like a dialog: focus moves into the list and stays there while it is open, Escape closes it, and focus returns to the toggle when it closes without a selection.

A top-of-page list without a sticky control is also fine on mobile for pages people read through. Add a "Back to contents" link at the end of each long section, so a reader can get back to the list without a long scroll.

Accessibility

A page with a sticky table of contents has at least two navigation regions, the site menu and the section list, and a screen-reader user moving by landmark hears both announced as "navigation" unless they are told apart. Give the section list its own name, for example aria-label="On this page" or aria-label="Table of contents".

Nested lists for second-level entries let assistive technology announce the hierarchy along with the links. Keep each link's text the same as its heading. If the sidebar has to shorten a long heading, any aria-label you add for the full heading must contain the visible short text, ideally at the start, or the link fails WCAG 2.5.3 Label in Name for people who operate the page by voice.

In-page navigation doesn't replace a skip link. A table of contents helps a reader move between sections; a skip link gets a keyboard user past the repeated site header to the content in the first place. Our guide to skip navigation links covers that piece.

Frequently asked questions

What is a table of contents in web design?

It is a list of links to the sections of the current page, generated from its headings. It previews the page's structure and lets a reader jump to a section without scrolling to find it.

Should a table of contents be sticky?

On long pages that people navigate around rather than read straight through, yes, on layouts that have room for it. A sticky list lets readers move between sections from anywhere and shows where they are. For pages read once from top to bottom, a list at the top is enough.

How many heading levels should a table of contents include?

One or two. Start with H2s and add H3s only when the top-level sections are long and contain subsections people look for directly. More than two levels turns the list into an outline that is hard to scan.

What is the difference between a table of contents and anchor links?

Anchor links are the mechanism: links that point to an id on the same page. A table of contents is a component built from them, a structured list covering every section in order. A single "Jump to pricing" link is an anchor link but not a table of contents.

Does a table of contents help SEO?

It can help a little. Descriptive section ids give search engines addresses for individual sections, and Google sometimes shows links that point to a specific part of a page. For readers, the benefit is landing on the answer they searched for instead of the top of a long page.

How do I make a table of contents in HTML?

Give each heading an id, then build a list of links to those ids inside a labelled <nav>. Most static site generators and content systems can generate the list from the page's headings at build time, which keeps it in sync when headings change.

For navigation between pages rather than within one, start with breadcrumb navigation patterns or browse the navigation design hub.

Free UX Snapshot for 50 Product Teams

Apply now and get a complimentary UX Snapshot — our rapid clarity audit delivered in 48 hours. Limited to the first 50 products.

Apply for Free UX Snapshot

Comments

Loading comments…

Related

Information Architecture

Zoom UX Teardown: From Meetings to Webinars Through the Products Menu

A UX teardown of Zoom's public Meetings and Webinars pages: prices in the Meetings hero, Webinars filed under Sales & Marketing, and a Webinars hero that asks for a sales call above three plans, each with its own Buy now button and the same 500-attendee cap.

TYPENORMLabs · 5 min · October 16, 2026

SaaS
Web
Information Architecture
Zoom UX Teardown: From Meetings to Webinars Through the Products Menu

Information Architecture

Twitch UX Teardown: Which Filters Survive a Tab Change

On Twitch's VALORANT directory, an English + Esports filter set survives the switch from Live Channels to Videos only in part: the Language badge comes along, the Esports chip doesn't, and nothing on the page says so.

TYPENORMLabs · 5 min · October 9, 2026

Web
Gaming
Information Architecture
Twitch UX Teardown: Which Filters Survive a Tab Change

AI Interaction

Perplexity UX Teardown: Who Is Behind the Citation Chip

A UX teardown of Perplexity's signed-out search: an answer whose citation chips show one domain and a +1, a Sources panel that fits two of ten sources, and a Links tab where three of the first six results sit on display makers' own sites.

TYPENORMLabs · 4 min · October 15, 2026

SaaS
Web
Conversational
Perplexity UX Teardown: Who Is Behind the Citation Chip