The Art of Page Anchors & Bookmarks
Transform your long-form content into an interactive roadmap. Master URL fragments, the :target pseudo-class, and advanced scroll-orchestration to create professional-grade Single Page Navigation.
Senior Engineering Insight: In modern web applications, anchors are the backbone of "Deep Linking." They allow users to share specific states or sections of a page, significantly increasing user retention and conversion rates by reducing the "search friction" within your UI.
1. The Anatomy of a URL Fragment
A page anchor relies on the URL Fragment (the part of a URL starting with the # character). Unlike query parameters (?id=123), fragments are handled entirely on theclient-side. The browser does not send the fragment to the server; it uses it to locate an element with a matching ID in the DOM.
Identifier Match
The browser searches for any element where id="fragment". If found, it calculates the element's coordinate and scrolls the viewport.
The Null Fragment (#)
A bare # is a special case that always scrolls the user back to the top of the <html> element.
Technical Note: Historically, developers used<a name="target"> for anchors. This isobsolete. Modern HTML5 uses the idattribute on any element as a valid anchor target.
2. The Strategic Table of Contents (TOC)
A Table of Contents is the most common implementation of anchors. Beyond mere navigation, it provides users with a visual mental map of your content's hierarchy.
| Feature | Static TOC | Sticky/Floating TOC |
|---|---|---|
| Visibility | Only at the top | Always visible while scrolling |
| UX Value | Good for initial overview | Excellent for switching sections mid-read |
| Implementation | Standard List of Links | CSS position: sticky |
Senior UX Tip: For extremely long technical articles, a sticky sidebar TOC with active state tracking(using Intersection Observer) is the gold standard. It tells the user exactly where they are in the "story" of your documentation.
Basic Anchor Links
Create anchor links using the hash symbol (#) followed by an element's ID:
<!DOCTYPE html>
<html>
<head>
<title>Anchor Links Demo</title>
</head>
<body>
<!-- Link that jumps to a section -->
<nav>
<a href="#introduction">Introduction</a>
<a href="#features">Features</a>
<a href="#pricing">Pricing</a>
<a href="#contact">Contact</a>
</nav>
<!-- Target sections with matching IDs -->
<section id="introduction">
<h2>Introduction</h2>
<p>Content here...</p>
</section>
<section id="features">
<h2>Features</h2>
<p>Content here...</p>
</section>
<section id="pricing">
<h2>Pricing</h2>
<p>Content here...</p>
</section>
<section id="contact">
<h2>Contact</h2>
<p>Content here...</p>
</section>
</body>
</html>How It Works:
- Add unique
idattribute to target element - Create link with
href="#id-name" - Clicking the link scrolls to that element
- URL updates to include the fragment (e.g.,
page.html#features)
Table of Contents Pattern
A common pattern for long articles and documentation:
<article>
<header>
<h1>Complete HTML Guide</h1>
<!-- Table of Contents -->
<nav aria-label="Table of contents">
<h2>Table of Contents</h2>
<ol>
<li><a href="#what-is-html">What is HTML?</a></li>
<li><a href="#getting-started">Getting Started</a></li>
<li><a href="#basic-tags">Basic Tags</a>
<ol>
<li><a href="#headings">Headings</a></li>
<li><a href="#paragraphs">Paragraphs</a></li>
<li><a href="#links">Links</a></li>
</ol>
</li>
<li><a href="#best-practices">Best Practices</a></li>
</ol>
</nav>
</header>
<main>
<section id="what-is-html">
<h2>What is HTML?</h2>
<p>HTML stands for HyperText Markup Language...</p>
</section>
<section id="getting-started">
<h2>Getting Started</h2>
<p>To begin with HTML...</p>
</section>
<section id="basic-tags">
<h2>Basic Tags</h2>
<h3 id="headings">Headings</h3>
<p>HTML has six heading levels...</p>
<h3 id="paragraphs">Paragraphs</h3>
<p>Paragraphs are created with...</p>
<h3 id="links">Links</h3>
<p>Links connect pages together...</p>
</section>
<section id="best-practices">
<h2>Best Practices</h2>
<p>Follow these guidelines...</p>
</section>
</main>
</article>"Back to Top" Button
Let users quickly return to the page top:
<body>
<!-- Add ID to top element -->
<header id="top">
<h1>Page Title</h1>
<nav>...</nav>
</header>
<main>
<!-- Long content -->
<p>Lots of content...</p>
</main>
<footer>
<!-- Back to top link -->
<a href="#top" class="back-to-top">⬆ Back to Top</a>
</footer>
</body>
<style>
.back-to-top {
display: inline-block;
padding: 0.75rem 1.5rem;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 5px;
transition: background-color 0.3s;
}
.back-to-top:hover {
background-color: #0056b3;
}
/* Fixed position button (alternative) */
.back-to-top.fixed {
position: fixed;
bottom: 20px;
right: 20px;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
</style>href="#" or href="#top"to scroll to the top, even without an ID. However, using an explicit ID is more semantic.Smooth Scrolling
Add smooth animated scrolling for better user experience:
CSS-Only Smooth Scrolling
/* Apply to entire page */
html {
scroll-behavior: smooth;
}
/* Or apply to specific container */
.scrollable-container {
scroll-behavior: smooth;
}/* Only apply smooth scrolling if user hasn't requested reduced motion */
@media (prefers-reduced-motion: no-preference) {
html {
scroll-behavior: smooth;
}
}
/* Instant scrolling for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}JavaScript Smooth Scrolling (More Control)
<a href="#section-2" class="smooth-scroll">Jump to Section 2</a>
<script>
document.querySelectorAll('a.smooth-scroll').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href');
const targetElement = document.querySelector(targetId);
targetElement.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
// Update URL without jumping
history.pushState(null, null, targetId);
});
});
</script>Scroll Offset for Fixed Headers
When you have a fixed header, anchors can scroll behind it. Fix this with CSS:
/* If you have a 60px fixed header */
header {
position: fixed;
top: 0;
height: 60px;
background: white;
z-index: 100;
}
/* Add scroll padding to account for fixed header */
html {
scroll-padding-top: 70px; /* Header height + 10px buffer */
}
/* Or apply to specific sections */
section {
scroll-margin-top: 70px;
}<section>
<!-- Invisible anchor positioned above visible heading -->
<span id="features" class="anchor-offset"></span>
<h2>Features</h2>
<p>Content...</p>
</section>
<style>
.anchor-offset {
display: block;
position: relative;
top: -70px; /* Negative offset for fixed header */
visibility: hidden;
}
</style>Highlighting Current Section
Automatically highlight the current section in your navigation:
<nav id="toc">
<a href="#section-1" class="toc-link">Section 1</a>
<a href="#section-2" class="toc-link">Section 2</a>
<a href="#section-3" class="toc-link">Section 3</a>
</nav>
<section id="section-1">...</section>
<section id="section-2">...</section>
<section id="section-3">...</section>
<style>
.toc-link.active {
font-weight: bold;
color: #007bff;
border-left: 3px solid #007bff;
padding-left: 8px;
}
</style>
<script>
const sections = document.querySelectorAll('section[id]');
const tocLinks = document.querySelectorAll('.toc-link');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Remove active class from all links
tocLinks.forEach(link => link.classList.remove('active'));
// Add active class to current section's link
const activeLink = document.querySelector(`.toc-link[href="#${entry.target.id}"]`);
if (activeLink) {
activeLink.classList.add('active');
}
}
});
}, {
rootMargin: '-20% 0px -70% 0px' // Trigger when section is in viewport
});
sections.forEach(section => observer.observe(section));
</script>FAQ Accordion with Anchors
Link directly to specific FAQ items:
<section class="faq">
<h2>Frequently Asked Questions</h2>
<div class="faq-item" id="faq-1">
<h3>
<a href="#faq-1">What is HTML?</a>
</h3>
<p>HTML stands for HyperText Markup Language...</p>
</div>
<div class="faq-item" id="faq-2">
<h3>
<a href="#faq-2">Is HTML a programming language?</a>
</h3>
<p>No, HTML is a markup language, not a programming language...</p>
</div>
<div class="faq-item" id="faq-3">
<h3>
<a href="#faq-3">Do I need to know CSS?</a>
</h3>
<p>While you can write HTML without CSS...</p>
</div>
</section>
<style>
.faq-item {
margin: 1.5rem 0;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 5px;
}
.faq-item:target {
background-color: #fff3cd;
border-color: #ffc107;
}
.faq-item h3 a {
color: inherit;
text-decoration: none;
}
.faq-item h3 a:hover {
text-decoration: underline;
}
</style>:targetto the element that matches the current URL fragment (the part after #).Styling the :target Element
Use CSS to highlight the targeted element:
/* Highlight targeted section */
section:target {
background-color: #ffffcc;
border-left: 5px solid #ffcc00;
padding-left: 15px;
animation: highlight 2s ease-out;
}
@keyframes highlight {
0% {
background-color: #ffff00;
}
100% {
background-color: #ffffcc;
}
}
/* Add indicator to targeted heading */
h2:target::before {
content: "→ ";
color: #007bff;
font-weight: bold;
}
/* Pulse animation for targeted element */
:target {
animation: pulse 1s ease-in-out;
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.02);
}
}ID Naming Best Practices
✅ Good ID Names
id="introduction"id="getting-started"id="section-2"id="contact-form"id="pricing-table"
⌠Bad ID Names
id="1st-section"(starts with number)id="my section"(contains space)id="section#2"(invalid character)id="Contact"(inconsistent casing)id="a"(not descriptive)
ID Rules:
- Must be unique on the page
- Must start with a letter (a-z, A-Z)
- Can contain letters, digits, hyphens, underscores, periods
- Case-sensitive
- No spaces allowed
- Should be descriptive and meaningful
6. The Psychology of Navigation: Why Anchors Matter
User experience studies show that visitors are often intimidated by massive walls of text. Anchors serve as "Safety Nets." When a user sees a Table of Contents or a "Jump to Summary" link, they feel more in control of their reading experience.
Information Scent
Descriptive anchors provide 'information scent'—clues that tell the user exactly what they will find when they click, reducing cognitive load.
The F-Pattern
Users scan in an 'F' pattern. Placing anchors at key inflection points allows scanners to stop and dive deep exactly where they need to.
UX Rule: Never make an anchor link go to a generic "Section 1" or "Part A". Use keyword-rich text like "Jump to Technical Requirements" to help with SEO and user confidence.
7. Performance: Scrolling & The Browser Paint Loop
Modern browsers optimize smooth scrolling, but poorly implemented JavaScript scroll listeners can cause Layout Thrashing. When an anchor link is clicked, the browser must:
- Calculate the target element's bounding box.
- Update the URL fragment.
- Orchestrate the scroll animation (if
smooth). - Fire
scrollandintersectionevents.
Performance Pro Tip: Use the CSSwill-change: transform; property on the body or scrolling container if you are implementing complex parallax effects alongside your anchors. This hints the browser to promote the layer to the GPU, ensuring 60fps scrolling.
8. Stateful Fragments: The Foundation of Client-Side Routing
In Single Page Applications (SPAs), the URL fragment is the primitive form of "State." By listening to the hashchangeevent, developers can create complex navigation systems without ever refreshing the page.
// Simple Router Logic
window.addEventListener('hashchange', () => {
const route = window.location.hash.slice(1) || 'home';
loadContentForRoute(route);
});
function loadContentForRoute(route) {
console.log(`Updating state for: ${route}`);
// Update DOM dynamically based on fragment
}9. Accessibility: The Focus Trap & Logical Flow
Scroll-jumping can disorient users, especially those using screen readers or keyboard navigation. When a user "jumps" to a section, the visual viewport moves, but the keyboard focusoften stays on the link they just clicked. This is a "Focus Trap."
Accessibility Requirement: Always ensure that after a jump, the target element (or its container) receives programmatic focus. This allows keyboard users to continue reading from the new location immediately.
10. Accessibility Best Practices
Make Anchors Accessible:
- Descriptive link text: "Jump to Features section" is better than "Click here"
- Keyboard navigation: All anchor links should be keyboard-accessible
- Focus management: Target element should receive focus after jump
- Skip links: Provide "Skip to content" for keyboard users
// Move focus to target element after scrolling
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// Scroll to element
targetElement.scrollIntoView({ behavior: 'smooth' });
// Set focus (make element focusable first if needed)
if (!targetElement.hasAttribute('tabindex')) {
targetElement.setAttribute('tabindex', '-1');
}
targetElement.focus();
}
});
});Common Use Cases Summary
📋 Documentation
Table of contents, section navigation, API reference
📰 Long Articles
Skip to sections, back to top, related articles
ðŸ›ï¸ E-commerce
Product sections, reviews, specifications
â“ FAQs
Direct links to specific questions
📱 Single Page Apps
Section navigation, tabs, accordions
📚 Educational
Course modules, lesson sections, exercises
Best Practices Summary
✅ Do
- Use descriptive, unique IDs
- Add smooth scrolling with
scroll-behavior - Respect
prefers-reduced-motion - Account for fixed headers with scroll offset
- Highlight targeted sections with
:target - Test keyboard navigation
- Manage focus after scrolling
- Provide back to top links for long pages
⌠Don't
- Use duplicate IDs on same page
- Start IDs with numbers
- Include spaces in IDs
- Forget about fixed header overlap
- Use anchor links for actions (use buttons)
- Ignore accessibility considerations
- Make anchor links too small to click
- Forget to test smooth scrolling