Only 13.7% of the top million websites have a skip navigation link. Of those that do, 10% are broken. This is a feature that takes about 20 minutes to implement, costs nothing to maintain, and directly improves the experience for every keyboard user who visits your site.
So what skip navigation actually does and why it matters beyond compliance checkboxes ?
What skip navigation actually solves
Picture a typical website. There's a logo, a main navigation with 8–15 links, maybe a search bar, maybe a language switcher or a dark mode toggle. A sighted mouse user glances past all of it and clicks directly on the content they want. The whole process takes less than a second.
Now picture navigating that same page with only a keyboard. Every one of those elements: logo, nav links, search input, toggles, requires a Tab keypress to move past. On a site with 20 interactive elements before the main content, that's 20 Tab presses. On every single page. For someone using a switch device by tapping their head, or operating a mouth stick to press keys, those 20 actions aren't a minor inconvenience. They're a physical barrier.

(image caption: A mouth stick: an assistive device gripped between the teeth to press keys and interact with a computer, used by people with limited or no use of their hands.)
A skip navigation link solves this by placing an anchor link as the very first focusable element on the page. When activated, it jumps the user directly to the main content. One Tab, one Enter, done.
Note: WCAG Success Criterion 2.4.1 (Bypass Blocks) requires that websites provide a mechanism to skip repeated content blocks. This is a Level A requirement, the most basic tier of accessibility compliance. It's referenced in the ADA (US) and the European Accessibility Act. It is not optional for any public-facing website that needs to meet accessibility standards.
Why this isn't just a compliance box
The practical argument for skip links goes beyond legal requirements.
Screen reader users hear every element announced sequentially. Without a bypass mechanism, they hear the same navigation announced on every page they visit. Keyboard-only users, whether due to motor impairments, temporary injuries, or simply preference, must physically tab through every repeated element. Users with cognitive or attention differences benefit from reduced noise between them and the content they came for.
And it's not a niche audience. WebAIM's 2025 analysis of the top million home pages found an average of 51 accessibility errors per page. The web is not built for these users by default, which means every correct implementation counts.
How it works
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Page Title</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Skip link: first element inside <body> -->
<a href="#main-content" class="skip-link">
Skip to content
</a>
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
<a href="/contact">Contact</a>
</nav>
</header>
<!-- Target: the main content area -->
<main id="main-content" tabindex="-1">
<h1>Page Heading</h1>
<p>Your actual content starts here.</p>
</main>
<footer>
<p>© 2025</p>
</footer>
</body>
</html>
Three things make this work: the skip link is the first focusable element in the DOM (it can be after the logo also), it points to an id on the main content container, and the target has tabindex="-1" so it can receive focus programmatically.
The tabindex="-1" part is critical. Without it, the element isn't focusable, the browser will scroll to it when the link is activated, but focus won't actually move there. The next Tab press would jump back to wherever focus was before (usually somewhere in the header), completely defeating the purpose.
Note: Use the semantic element as your target, not a random . This serves double duty: it's the skip link target and an ARIA landmark that screen readers can navigate to independently using their built-in landmark navigation.
The link is hidden off-screen by default and slides into view when it receives keyboard focus. When the user Tabs away, it slides back out. Mouse users never see it unless they're also using the keyboard.
.skip-link {
position: absolute;
top: -100%;
left: 16px;
z-index: 9999;
padding: 12px 24px;
background-color: #0a0e17;
color: #ffffff;
font-size: 1rem;
font-weight: 600;
text-decoration: none;
border-radius: 0 0 8px 8px;
transition: top 0.2s ease-in-out;
}
.skip-link:focus {
top: 0;
outline: 2px solid #ffffff;
outline-offset: 2px;
}
Common mistakes and how to avoid them
The pattern above is simple, but there are several ways to break it. The WebAIM Million 2025 study found that 10% of existing skip links were non-functional. The following mistakes are among the most common causes of broken or ineffective skip links.
- Hiding with display: none or visibility: hidden
This is the most widespread error. An element with display: none is removed from both the layout and the tab order, it cannot receive keyboard focus at all. That means you can't pair it with a :focus rule to make it appear when the user Tabs to it, because the user can never Tab to it in the first place. visibility: hidden has the same practical effect: the element takes up layout space but is removed from the tab order and invisible to assistive technology.
It is technically possible to use JavaScript to toggle display: none on and off in response to some external trigger (a keypress event, a :focus-within on a parent wrapper, etc.). But this adds unnecessary complexity for a problem that CSS positioning solves cleanly without scripting.
The same problem applies to subtler hiding techniques: setting the link to 0px width or height, making it fully transparent, or matching its color to the background. Automated testing tools flag all of these as failures.
Fix: Hide the link with CSS positioning (top: -100% or left: -9999px) so it stays in the DOM and the tab order. Bring it back into view on :focus. No JavaScript needed.
- The target element isn't focusable
If the skip link points to an id on a
or without tabindex="-1", activating the link scrolls the page to that element, but focus doesn't move there. The user sees the content scroll into view, presses Tab, and focus jumps back to the header. From their perspective, the skip link did nothing.Fix: Always add tabindex="-1" to the target element. This makes it programmatically focusable without adding it to the natural tab order.
- Buried under a sticky header:
Sites often have a fixed or sticky header with position: fixed and a high z-index. When the skip link appears on focus, it renders behind the header, present in the DOM, technically visible in the layout, but completely obscured on screen. This one is particularly frustrating because the skip link works perfectly in terms of functionality (focus moves, keyboard interaction works), but sighted keyboard users can't see it.
Fix: Give the skip link a z-index value higher than your header. If your header uses z-index: 100, the skip link needs at least z-index: 101. The example CSS above uses 9999 to avoid this issue entirely.
When you might not need one
Skip links are strongly recommended, but there are contexts where they add less value. A page with very minimal navigation: a landing page with two links in the header, doesn't force keyboard users through a burdensome number of Tab stops. Adding a skip link is still good practice, but the impact is smaller.
Single-purpose pages with no repeated navigation (a login form for example) may not have content blocks to skip in the first place.
Skip navigation is one of the simplest accessibility features to implement. It's a single component, a few lines of CSS, and a tabIndex attribute. The fact that 86% of the top million websites still don't have one isn't a technical problem, it's an awareness problem.
If you're reading this and your site doesn't have a skip link yet: add one. It will take you less time than reading this article did.
Further reading
WebAIM: Skip Navigation Links
WCAG 2.1 SC 2.4.1: Bypass Blocks
WebAIM Million 2025: annual report on the state of web accessibility
For more content like this, check devly.digital
Top comments (0)