// pattern
Accessible Skip Links Pattern
A navigation bypass mechanism that lets keyboard users skip past repetitive content and jump directly to the main content area. Pure HTML and CSS — no JavaScript required.
// 01 · live demo
Live Demo
Click inside the demo area below, then press Tab to see the skip link appear.
Main Content Area
This is the main content. When you activated the skip link, focus jumped here — bypassing all 5 navigation links above. Without the skip link, a keyboard user would have to Tab through every nav link on every single page load.
// 02 · the code
The Code
<body>
<!-- Skip link: MUST be the first focusable element -->
<a href="#main-content" class="skip-link">
Skip to main content
</a>
<header>
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>
<!-- Target: tabindex="-1" makes non-interactive
elements focusable programmatically -->
<main id="main-content" tabindex="-1">
<h1>Page Title</h1>
<p>Your content here.</p>
</main>
</body>
/* Visually hidden by default, visible on focus.
Do NOT use display:none or visibility:hidden —
those hide the link from screen readers too. */
.skip-link {
position: absolute;
top: -100%;
left: 1rem;
background: var(--color-primary);
color: #ffffff;
padding: 0.5rem 1rem;
border-radius: 0 0 0.5rem 0.5rem;
font-weight: 600;
text-decoration: none;
z-index: 1000;
transition: top 150ms ease;
}
.skip-link:focus {
top: 0;
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
/* Remove the focus ring on the target element
since it's not an interactive element */
main:focus {
outline: none;
}
// 03 · why these decisions
Why These Decisions
Why visually hidden instead of truly hidden?
The skip link needs to be accessible to screen reader users at all times — they hear it announced when they land on the page. Using display: none or visibility: hidden removes the element from the accessibility tree entirely, defeating the purpose. The position: absolute; top: -100% technique keeps the link in the DOM and accessible to assistive technology while hiding it visually until it receives focus.
Why must it be the first focusable element?
The whole point of a skip link is to save keyboard users from tabbing through repetitive content. If the skip link comes after the navigation, the user has already tabbed through everything they wanted to skip. The skip link must be the very first <a> or <button> in the DOM — before the header, before the nav, before everything.
Why tabindex="-1" on the target?
When a user clicks an anchor link (href="#main-content"), the browser scrolls to the target element. But for focus to actually move there (which is what keyboard users need), the target element must be focusable. The <main> element is not natively focusable, so tabindex="-1" makes it programmatically focusable — it can receive focus via the anchor link, but it won't appear in the regular tab order.
Why use top: -100% instead of the .visually-hidden class?
The .visually-hidden utility (using clip and 1px sizing) works well for content that should never be seen. But skip links need to become visible on focus. Using position: absolute with top lets us animate the link into view smoothly, giving sighted keyboard users a clear visual indicator of what's happening.
Why "Skip to main content" as the text?
The link text should clearly describe what will happen when activated. "Skip to main content" is the most widely understood convention. Avoid vague labels like "Skip" or technical labels like "Jump to #main". Some sites add multiple skip links (e.g., "Skip to search," "Skip to footer") — keep these to a minimum to avoid cluttering the experience.
// 04 · keyboard interaction
Keyboard Interaction
| Key | Action |
|---|---|
| Tab | The very first Tab press on the page focuses the skip link, making it visible |
| Enter | Activates the skip link, scrolling to and focusing the main content area |
| Tab (after skip) | The next Tab after activating the skip link moves focus to the first focusable element inside main content |
| Tab (without activating) | If the user presses Tab again without activating the skip link, focus moves to the next element (typically the first nav link) |
// 05 · wcag 2.2 success criteria
WCAG 2.2 Success Criteria
This pattern satisfies the following WCAG 2.2 success criteria:
- 2.4.1 Bypass Blocks Level A — Provides a mechanism to bypass blocks of content that are repeated on multiple pages. This is the primary criterion skip links address.
- 2.1.1 Keyboard Level A — The skip link is fully operable via keyboard. It appears on focus and activates with Enter.
- 2.4.3 Focus Order Level A — The skip link is the first focusable element, and activating it moves focus to a logical position in the content.
- 2.4.4 Link Purpose (In Context) Level A — The link text "Skip to main content" clearly describes the link's destination and purpose.
- 2.4.11 Focus Not Obscured (Minimum) Level AA — The skip link slides into view on focus and is not covered by other elements.
- 2.4.13 Focus Appearance Level AAA — The focus indicator uses a 2px solid outline with sufficient contrast.
- 2.5.8 Target Size (Minimum) Level AA — The skip link has sufficient padding to exceed the 24x24px minimum target area.
// 06 · screen reader behavior
Screen Reader Behavior
When the page loads
- NVDA: "Skip to main content, link" — announced as the first interactive element when the user starts tabbing
- JAWS: "Skip to main content, link" — same behavior, announced immediately on first Tab press
- VoiceOver: "Skip to main content, link" — announced when the user enters keyboard navigation with Tab
When the skip link is activated
After pressing Enter on the skip link, screen readers move their virtual cursor to the target element. The next element the user encounters will be within the main content area, effectively bypassing all header and navigation content.
- NVDA: Announces the target element (e.g., the first heading in main content)
- JAWS: Announces "main landmark" if the target is a
<main>element, then reads content from that point - VoiceOver: Announces the target element and its role; subsequent navigation continues from that position
For screen reader users browsing sequentially
Screen reader users navigating with arrow keys (not Tab) will also encounter the skip link as the first piece of content. This is useful — even users who navigate by headings or landmarks benefit from knowing the skip link exists.
// 07 · common mistakes
Common Mistakes
display: none to hide the skip link
display: none and visibility: hidden remove the element from the accessibility tree. Screen readers can't find it, and keyboard users can't focus it. The skip link becomes completely invisible to everyone — the opposite of what you want. Use positional hiding (position: absolute; top: -100%) instead.
<main> or a <div>) doesn't have tabindex="-1", some browsers will scroll to it but not move focus. The user presses Tab and ends up back in the header. Always add tabindex="-1" to non-interactive target elements.
id on the target
If the href="#main-content" doesn't match an element with id="main-content", the link does nothing. This is easy to break during refactors. Always verify that the skip link's href matches an actual id in the page.
.visually-hidden without a :focus override. This helps screen reader users but not sighted keyboard users, who also need to bypass repetitive content. Always make the skip link visible when focused.
// 08 · native html vs. aria
Native HTML vs. ARIA
Skip links are a great example of native HTML doing exactly what's needed. No ARIA attributes are required at all.
| Feature | <a href="#id"> (native) |
ARIA-based approach |
|---|---|---|
| Link semantics | Automatic — it's an <a> element |
Would need role="link" on a non-link element |
| Keyboard focusable | Automatic — anchor links are focusable by default | Would need tabindex="0" |
| Click/Enter activation | Automatic — native link behavior | Would need JavaScript keydown/click handlers |
| Scroll to target | Automatic — browser handles fragment navigation | Would need JavaScript scrollIntoView() |
| Move focus to target | Automatic with tabindex="-1" on target |
Would need JavaScript element.focus() |
| Screen reader announcement | Automatic — "link" role announced | Would need role="link" + accessible name |
| JavaScript required | None | Yes — for navigation, focus, and activation |
| Browser support | All browsers, always | Depends on ARIA support |