Fragment Identifiers in HTML
Learn how fragment identifiers in HTML work to create jump links, smooth scroll navigation, tables of contents, skip links, and improving user navigation experience.
You’ve probably clicked a “Jump to Section” link on a long article and watched the page scroll straight to where you needed to be, without loading anything new. Or maybe you’ve seen a Wikipedia-style table of contents where every item takes you to a specific part of the page.
That’s fragment identifiers at work. And once you understand how they work, you’ll use them on basically every long page you build.
In this tutorial, we’ll go from zero to a complete working navigation system. We’ll cover what fragment identifiers actually are, how to write jump links, how to build a table of contents, how to add smooth scroll HTML behavior, how to fix the sticky header problem, and how to set up skip links for accessibility. All practical, all useful.
01What Are Fragment Identifiers in HTML?
A fragment identifier is the #something part at the end of a URL.
Take this URL:
https://daniyaldev.com/html-guide/#getting-started
The #getting-started part is the fragment identifier. When a browser sees this, it doesn’t make a new request to the server for that fragment. It simply finds the element on the current page that has id="getting-started" and scrolls to it.
The page doesn’t reload. The URL updates. The user lands right where they need to be.
That’s the entire core concept. Everything else in this tutorial builds on this one idea.
How Fragment Identifiers Connect
About Us
</a>
points to
…
</section>
href="#about" matches id="about", the # is stripped, the rest must match exactly
02The id Attribute Is Your Jump Target
For any fragment link to work, there has to be something to land on. That something is an HTML element with an id attribute.
If you’ve already read our guide on HTML tags, elements, and attributes, you know that id is a global attribute. That means it can go on almost any element: <section>, <div>, <h2>, <article>, whatever makes sense.
Before writing your first jump link, there are four rules worth memorizing:
- IDs must be unique on the page. No two elements can share the same ID. If they do, the browser picks the first one it finds, and your other links break silently.
- IDs are case-sensitive.
href="#Contact"will not matchid="contact". Keep everything lowercase to stay consistent. - No spaces in IDs. Use hyphens to separate words.
id="quick-start"works.id="quick start"will not. - Start with a letter or underscore, not a number.
id="section-1"is fine.id="1-section"is technically invalid HTML.
03Writing Your First Jump Link
A jump link is an anchor tag where the href starts with #. The value after the # must exactly match the id of the target element.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Jump Links Demo</title>
</head>
<body>
<nav>
<a href="#about">About</a>
<a href="#services">Services</a>
<a href="#contact">Contact</a>
</nav>
<section id="about">
<h2>About Us</h2>
<p>We build things on the web.</p>
</section>
<section id="services">
<h2>Our Services</h2>
<p>Websites, apps, and everything in between.</p>
</section>
<section id="contact">
<h2>Contact Us</h2>
<p>Email us at [email protected]</p>
</section>
</body>
</html>
Click “About” and the browser scrolls to id="about". Click “Services” and it goes to id="services". No JavaScript, no libraries, just HTML.
One thing to pay attention to: the # in href="#about". Without it, the browser treats about as a relative path and tries to navigate to a separate page called about. The # is what signals a fragment link. For a deeper look at how anchor tags and href work, we covered it thoroughly in our anchor tags tutorial.
04Building a Table of Contents with Fragment Identifiers
A table of contents is just a list of jump links. That’s really all it is. You make a list at the top of the page, and each item points to a section below using a fragment identifier.
<nav aria-label="Table of Contents">
<h2>In This Article</h2>
<ol>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#installation">Installation</a></li>
<li><a href="#configuration">Configuration</a></li>
<li><a href="#examples">Examples</a></li>
<li><a href="#faq">FAQ</a></li>
</ol>
</nav>
<section id="introduction">
<h2>Introduction</h2>
<p>Let's start with the basics...</p>
</section>
<section id="installation">
<h2>Installation</h2>
<p>Here's how to get set up...</p>
</section>
<!-- and so on -->
A few things worth noting here. First, the <nav> element wraps the TOC because a table of contents is navigation. That’s the semantically correct choice. We went deep on this in our semantic HTML guide, and using <nav> for a TOC is one of the clearest use cases for it.
Second, the aria-label="Table of Contents" attribute tells screen readers what this navigation block is for, which is important when a page has multiple <nav> elements (like a main menu and a TOC sidebar).
Third, and most obviously: the href values on the links must match the id values on the sections. href="#installation" connects to id="installation". No # on the ID side, only on the link side.
Introduction
This is the intro section. On a real page your content lives here. The TOC on the left mirrors this section’s id in its href.
Installation
Click “Installation” in the TOC on the left and notice the active highlight update and the smooth scroll to this section.
Configuration
Each section has a unique id. The TOC links use href=”#that-id”. That’s the entire mechanism behind fragment identifiers.
FAQ
Last section. This pattern: TOC above, sections below, all connected by ids, is how documentation sites, long articles, and wikis are built.
05Good Habits for Naming IDs
Fragment identifiers end up in the URL. When someone shares a link to a specific section, they share something like:
https://daniyaldev.com/guide/#getting-started
That’s clean and readable. #s3 or #sec_01_A is not. Good ID names make your deep links shareable and understandable.
Follow this pattern: lowercase, hyphenated, descriptive.
<!-- Good -->
<section id="getting-started">...</section>
<section id="faq">...</section>
<section id="pricing-table">...</section>
<!-- Avoid -->
<section id="s1">...</section>
<section id="Section_One">...</section>
<section id="123-section">...</section>
There’s also a natural connection between your IDs and your headings. If a section has <h2>Getting Started</h2>, then id="getting-started" is an obvious and clean choice. This matters for page structure in general, our HTML headings guide covers how heading hierarchy and section labeling work together.
06Smooth Scroll HTML: Making It Feel Natural
By default, fragment navigation is instant. Click a link and the page teleports to the target. No animation. No context. On a short page that’s fine, but on a longer one it’s disorienting. Users can’t tell how far they moved or where they are on the page.
Smooth scrolling fixes this. Instead of jumping, the page animates to the target. The user sees the page travel, which keeps them oriented.
The simplest way to add smooth scroll HTML behavior is one line of CSS:
html {
scroll-behavior: smooth;
}
Put this in your stylesheet and every fragment link on the page will animate instead of jumping. The browser handles the animation entirely.
Here it is in a working example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Smooth Scroll Example</title>
<style>
html {
scroll-behavior: smooth;
}
</style>
</head>
<body>
<nav>
<a href="#part-one">Part One</a>
<a href="#part-two">Part Two</a>
<a href="#part-three">Part Three</a>
</nav>
<section id="part-one" style="height: 100vh; padding: 2rem;">
<h2>Part One</h2>
</section>
<section id="part-two" style="height: 100vh; padding: 2rem;">
<h2>Part Two</h2>
</section>
<section id="part-three" style="height: 100vh; padding: 2rem;">
<h2>Part Three</h2>
</section>
</body>
</html>
The scroll-behavior: smooth on the html element applies to the root scroll container, which is the full page. Browser support is excellent across Chrome, Firefox, Edge, and Safari.
A Small But Important Accessibility Note
Some users have their operating system configured to reduce motion. This is often for vestibular disorders, motion sensitivity, or just personal preference. If you apply smooth scrolling globally and ignore this setting, you’re overriding a deliberate accessibility choice.
The fix is a media query:
html {
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
Smooth scrolling for everyone who wants it. Instant navigation for those who prefer it. Two lines of CSS, and your site genuinely respects user preferences.
Instant Jump vs Smooth Scroll
Without scroll-behavior
Top
Target
With scroll-behavior: smooth
Top
Target
07JavaScript Smooth Scrolling for More Control
The CSS approach works great for most cases. But sometimes you need to trigger scrolling from JavaScript. Maybe a button runs some logic before scrolling, or you’re building a custom navigation component. That’s where scrollIntoView() comes in.
scrollIntoView()
Call this method on any DOM element and the page scrolls to it:
<button onclick="jumpToSection()">Go to Features</button>
<section id="features">
<h2>Features</h2>
<p>You scrolled here using JavaScript!</p>
</section>
<script>
function jumpToSection() {
const section = document.getElementById('features');
section.scrollIntoView({ behavior: 'smooth' });
}
</script>
The { behavior: 'smooth' } is what enables the animation. Leave it out and the browser jumps instantly.
You can also control how the element aligns within the viewport:
// Align element to the top of the viewport
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
// Center the element in the viewport
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Align element to the bottom of the viewport
element.scrollIntoView({ behavior: 'smooth', block: 'end' });
window.scrollTo() for Pixel-Based Scrolling
When you need to scroll to a specific pixel position rather than an element, use window.scrollTo():
// Scroll to 800px from the top of the page
window.scrollTo({
top: 800,
behavior: 'smooth'
});
// Back to top button
window.scrollTo({
top: 0,
behavior: 'smooth'
});
For “Back to Top” buttons, this is exactly what you’d use. For targeting specific sections, scrollIntoView() is usually cleaner because it doesn’t require you to know exact pixel positions, which change as content shifts.
08The Fixed Header Problem and How scroll-margin-top Fixes It
Here’s something that trips up almost every developer at some point. You set up a sticky navigation bar at the top of the page. You add your fragment identifiers. You click a jump link. And the section heading disappears behind the fixed header.
The browser scrolled to the element correctly. But since the sticky header covers the top of the viewport, the beginning of your section is hidden underneath it.
It’s one of those bugs that’s obvious once you know what’s happening but completely baffling the first time you see it.
The CSS fix is one line:
section {
scroll-margin-top: 80px;
}
The scroll-margin-top property adds extra space above an element specifically when it’s being scrolled to as a fragment target. It has no effect on the visual layout or spacing. It only changes where the browser stops when using fragment navigation.
Match the value to your header height. If your sticky nav is 70px, use scroll-margin-top: 80px (header height plus a small buffer for breathing room).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fixed Header Navigation</title>
<style>
html { scroll-behavior: smooth; }
/* Sticky nav */
header {
position: sticky;
top: 0;
height: 70px;
background: #1e293b;
display: flex;
align-items: center;
padding: 0 2rem;
gap: 1.5rem;
z-index: 100;
}
header a { color: #cbd5e1; text-decoration: none; }
header a:hover { color: white; }
/* This is the key fix */
section {
scroll-margin-top: 84px; /* header height + small gap */
padding: 3rem 2rem;
min-height: 80vh;
}
</style>
</head>
<body>
<header>
<a href="#about">About</a>
<a href="#services">Services</a>
<a href="#contact">Contact</a>
</header>
<section id="about">
<h2>About Us</h2>
<p>This heading is now fully visible below the sticky header.</p>
</section>
<section id="services">
<h2>Services</h2>
<p>Same here. Nothing gets cut off.</p>
</section>
<section id="contact">
<h2>Contact</h2>
<p>Everything lines up correctly.</p>
</section>
</body>
</html>
The Fixed Header Overlap: Problem and the Fix
Heading hidden behind header
/* no scroll-margin-top */
}
Section visible below header
scroll-margin-top: 52px;
}
09Skip Navigation Links: The Accessibility Part You Shouldn’t Skip
This is the section most tutorials leave out. And it’s one of the most important uses of fragment identifiers.
When someone navigates a website using only a keyboard (which is common for people with motor disabilities, or anyone who just prefers keyboard navigation), they tab through every interactive element on the page in order. On a page with a typical navigation bar, that means pressing Tab maybe 10 to 15 times through all the nav links before reaching the actual content. Every single page visit.
A skip link solves this. It’s a fragment link that sits as the very first element in the page body. When a keyboard user starts tabbing, this link appears. They can press Enter to jump straight to the main content, bypassing the entire nav.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Page with Skip Navigation Link</title>
<style>
/* Hidden by default */
.skip-link {
position: absolute;
top: -100%;
left: 1rem;
background: #6366f1;
color: white;
padding: 0.75rem 1.5rem;
text-decoration: none;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 600;
z-index: 9999;
transition: top 0.15s ease;
}
/* Visible when focused (keyboard Tab) */
.skip-link:focus {
top: 1rem;
}
</style>
</head>
<body>
<!-- This must be the very first element in body -->
<a href="#main-content" class="skip-link">Skip to Main Content</a>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
<a href="/contact">Contact</a>
</nav>
<main id="main-content">
<h1>Article Title</h1>
<p>Keyboard users land here immediately, skipping all nav links.</p>
</main>
</body>
</html>
The CSS key here is top: -100%, which pushes the link off-screen when it’s not focused. When keyboard focus lands on it, top: 1rem pulls it into view. Mouse users never see it. Keyboard users see it right away on their first Tab press.
The skip link is just a regular fragment identifier link. href="#main-content" points to id="main-content" on the <main> element. Same mechanism as all the other jump links we’ve built. The proper use of <main> as a landmark element is covered in our HTML document structure guide.
If you’re building anything real, add a skip link. It’s five minutes of work that makes a genuine difference for keyboard users.
10How the URL Changes with Fragment Navigation
When someone clicks a fragment link, the browser updates the URL to include the fragment. If they’re on /article/ and click a link to #faq, the URL becomes /article/#faq.
This creates a few useful behaviors:
Shareable deep links. Users can copy the URL and share it. The person who receives it lands directly on that section. This is massive for documentation, long how-to articles, and FAQs.
Back button works. Each fragment navigation adds an entry to the browser’s history stack. Pressing Back steps through those entries as expected.
Reload returns to the section. If a user refreshes the page while the URL contains a fragment, the browser reloads and scrolls back to that section automatically.
You can also link to a section on a completely different page. Just append the fragment to any URL:
<!-- Full URL to a section on another site -->
<a href="https://daniyaldev.com/html-guide/#getting-started">Getting Started</a>
<!-- Relative URL to a section on another page in your site -->
<a href="/html-guide/#getting-started">Getting Started</a>
The same rule applies: the fragment must match an id on that page. For controlling how links open (same tab, new tab), and how to handle security with external links, take a look at our link attributes tutorial.
11Common Mistakes to Avoid
These are the fragment identifier errors that show up most often, especially when starting out.
Forgetting the # in href
<!-- Wrong: browser tries to navigate to a page named "services" -->
<a href="services">Services</a>
<!-- Right: browser jumps to id="services" on the current page -->
<a href="#services">Services</a>
Case Mismatch Between href and id
<!-- Wrong: "AboutUs" and "aboutus" are different -->
<a href="#AboutUs">About</a>
<section id="aboutus"></section>
<!-- Right: exact match, both lowercase -->
<a href="#about-us">About</a>
<section id="about-us"></section>
Duplicate IDs on the Same Page
<!-- Wrong: browser picks the first one, second link breaks -->
<section id="intro">Part One Intro</section>
<section id="intro">Part Two Intro</section>
<!-- Right: unique IDs for every section -->
<section id="part-one-intro">Part One Intro</section>
<section id="part-two-intro">Part Two Intro</section>
Spaces in ID Values
<!-- Wrong: spaces in id break the fragment -->
<section id="getting started"></section>
<!-- Right: use hyphens -->
<section id="getting-started"></section>
Sticky Header Without scroll-margin-top
If your page has a fixed or sticky header and you’re using fragment navigation, always add scroll-margin-top to your jump target elements. Match the value to your header height. Without it, the first chunk of each section will be hidden behind the header every time.
12A Complete Page Putting It All Together
Here’s everything in one place: a sticky header, a sidebar table of contents, smooth scroll HTML behavior, scroll-margin-top, and a skip link. This is a solid, production-ready pattern you can adapt for any multi-section page. Just copy, paste it in a .html file and open it in your browser to see the complete example, because of limitations of the sandbox environment, it may not work correctly in our Online HTML Editor
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Complete Navigation Example</title>
<style>
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
}
body { font-family: sans-serif; margin: 0; background: #f8fafc; color: #1e293b; }
/* Skip link */
.skip-link {
position: absolute;
top: -100%;
left: 1rem;
background: #6366f1;
color: white;
padding: 0.75rem 1.5rem;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
z-index: 9999;
}
.skip-link:focus { top: 1rem; }
/* Sticky header */
.site-header {
position: sticky;
top: 0;
height: 64px;
background: #1e293b;
display: flex;
align-items: center;
padding: 0 2rem;
gap: 1.5rem;
z-index: 100;
}
.site-header a { color: #cbd5e1; text-decoration: none; font-size: 0.9rem; }
.site-header a:hover { color: white; }
/* Two-column layout */
.layout {
max-width: 960px;
margin: 0 auto;
padding: 2rem;
display: grid;
grid-template-columns: 200px 1fr;
gap: 3rem;
align-items: start;
}
/* Sticky sidebar TOC */
.toc {
position: sticky;
top: 80px;
background: #1e293b;
border-radius: 12px;
padding: 1.5rem;
color: white;
}
.toc-title {
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #6366f1;
margin: 0 0 1rem 0;
}
.toc ol { margin: 0; padding-left: 1.25rem; }
.toc li { margin-bottom: 0.5rem; }
.toc a { color: #94a3b8; text-decoration: none; font-size: 0.85rem; }
.toc a:hover { color: white; }
/* Content sections */
section {
scroll-margin-top: 80px;
padding-bottom: 3rem;
margin-bottom: 2rem;
border-bottom: 1px solid #e2e8f0;
min-height: 60vh;
}
section:last-child { border-bottom: none; }
@media (max-width: 640px) {
.layout { grid-template-columns: 1fr; }
.toc { position: static; }
}
</style>
</head>
<body>
<!-- Skip link: first element in body -->
<a href="#main-content" class="skip-link">Skip to Main Content</a>
<!-- Sticky navigation -->
<header class="site-header">
<a href="#overview">Overview</a>
<a href="#setup">Setup</a>
<a href="#usage">Usage</a>
<a href="#faq">FAQ</a>
</header>
<div class="layout">
<!-- Sidebar Table of Contents -->
<nav class="toc" aria-label="Table of Contents">
<h2 class="toc-title">On This Page</h2>
<ol>
<li><a href="#overview">Overview</a></li>
<li><a href="#setup">Setup</a></li>
<li><a href="#usage">Usage</a></li>
<li><a href="#faq">FAQ</a></li>
</ol>
</nav>
<!-- Main content with sections -->
<main id="main-content">
<section id="overview">
<h2>Overview</h2>
<p>This section sits correctly below the sticky header thanks to scroll-margin-top.</p>
</section>
<section id="setup">
<h2>Setup</h2>
<p>Same here. Smooth scrolling, fixed header offset, shareable URL.</p>
</section>
<section id="usage">
<h2>Usage</h2>
<p>The TOC, header links, and fragment identifiers all point to these IDs.</p>
</section>
<section id="faq">
<h2>FAQ</h2>
<p>Keyboard users can skip the nav entirely with the skip link at the top.</p>
</section>
</main>
</div>
</body>
</html>
13Quick Reference: Fragment Identifiers Cheat Sheet
Fragment Identifiers Quick Reference
html { scroll-behavior: auto; }
}
Skip to Main Content
</a>
behavior: ‘smooth’
});
View Section
</a>
14You’re Ready to Build Better Navigation
Fragment identifiers are one of those fundamentals that seem small until you realize how much of the web depends on them. Table of contents, in-page navigation, documentation sidebars, skip links, deep-link sharing, all of it runs on id and href="#".
Add scroll-behavior: smooth and you’ve got navigation that feels polished. Add scroll-margin-top and sticky headers stop swallowing your content. Add a skip link and keyboard users can actually use your page without frustration.
These aren’t advanced techniques. They’re just good habits. And once you start using them consistently, they’ll become second nature.
Fragment Identifiers in HTML
Loading challenge…
Ready to Practice?
Select a difficulty level above to load your challenge.