On This Page
The "On this page" navigation provides a table of contents for long pages, allowing users to quickly jump to specific sections.
When to use
Use this pattern when:
- A page has 3 or more distinct sections with headings
- The content is longer than 2-3 screen heights
- Users may want to navigate directly to specific information
- The page contains reference or documentation content
When not to use
- On short pages where all content is visible
- On index or overview pages with card-based navigation
- When there are fewer than 3 sections
- On transactional pages (forms, checkout flows)
Structure
The component consists of:
- Title - "On this page" heading
- Link list - Anchor links to each section heading
- Active indicator - Highlights the current section while scrolling
Example
Placement
Position the "On this page" navigation in a sticky sidebar to the right of the main content. This keeps it accessible while scrolling without interfering with the primary reading flow.
- Desktop (1200px+): Right sidebar, sticky position
- Tablet/Mobile: Hidden or collapsed into a dropdown at the top of the content
Design Specifications
| Element | Property | Value |
|---|---|---|
| Container | Width | 220-280px |
| Container | Position | sticky, top: var(--space-8) |
| Title | Font size | var(--text-sm) |
| Title | Text transform | uppercase |
| Title | Letter spacing | 0.05em |
| Title | Colour | var(--text-secondary) |
| List | Border left | 2px solid var(--border-default) |
| Link | Padding | var(--space-2) var(--space-3) |
| Link | Colour | var(--text-secondary) |
| Link hover | Colour | var(--text-primary) |
| Active link | Colour | var(--color-primary) |
| Active link | Border left | 2px solid var(--color-primary) |
| Active link | Font weight | var(--weight-medium) |
| Nested items (h3) | Padding left | var(--space-3) additional |
Heading Levels
The navigation should reflect the document structure:
- h2 headings - Top-level items, no indent
- h3 headings - Nested items, indented under their parent h2
- Deeper headings (h4+) are typically not included to keep the navigation concise
Scroll Behaviour
- Use
scroll-behavior: smoothfor animated scrolling to anchors - Account for fixed headers with
scroll-margin-topon target headings - Update the active indicator as the user scrolls using Intersection Observer
- The first visible heading in the viewport should be highlighted
Accessibility
- Wrap in
<nav>witharia-label="On this page" - All links must have descriptive text matching the heading
- Target headings must have
idattributes for anchor linking - Focus is moved to the target heading when a link is clicked
- Ensure keyboard navigation works correctly
HTML Structure
<nav class="on-this-page" aria-label="On this page">
<h4 class="on-this-page-title">On this page</h4>
<ul class="on-this-page-list">
<li class="on-this-page-item">
<a href="#section-1" class="on-this-page-link active">
Section One
</a>
</li>
<li class="on-this-page-item">
<a href="#section-2" class="on-this-page-link">
Section Two
</a>
</li>
<li class="on-this-page-item nested">
<a href="#subsection" class="on-this-page-link">
Subsection (h3)
</a>
</li>
<li class="on-this-page-item">
<a href="#section-3" class="on-this-page-link">
Section Three
</a>
</li>
</ul>
</nav>CSS Classes
.on-this-page {
position: sticky;
top: var(--space-8);
max-height: calc(100vh - var(--space-16));
overflow-y: auto;
padding: var(--space-4);
font-size: var(--text-sm);
}
.on-this-page-title {
font-size: var(--text-sm);
font-weight: var(--weight-semibold);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0 0 var(--space-3) 0;
}
.on-this-page-list {
list-style: none;
margin: 0;
padding: 0;
border-left: 2px solid var(--border-default);
}
.on-this-page-item {
margin: 0;
}
.on-this-page-item.nested {
padding-left: var(--space-3);
}
.on-this-page-link {
display: block;
padding: var(--space-2) var(--space-3);
margin-left: -2px;
border-left: 2px solid transparent;
color: var(--text-secondary);
text-decoration: none;
transition: color 100ms ease, border-color 100ms ease;
}
.on-this-page-link:hover {
color: var(--text-primary);
}
.on-this-page-link.active {
color: var(--color-primary);
border-left-color: var(--color-primary);
font-weight: var(--weight-medium);
}
/* Hide on smaller screens */
@media (max-width: 1199px) {
.on-this-page {
display: none;
}
}JavaScript Behaviour
// Use Intersection Observer to track visible sections
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Update active link based on visible heading
setActiveId(entry.target.id);
}
});
},
{ rootMargin: '-80px 0px -80% 0px' }
);
// Observe all heading elements
document.querySelectorAll('h2, h3').forEach((heading) => {
observer.observe(heading);
});