// 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.

native-html zero-js wcag-2.2-aa

// 01 · live demo

Live Demo

Click inside the demo area below, then press Tab to see the skip link appear.

Skip to main content

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

HTML
<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>
CSS
/* 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;
}
No JavaScript needed Skip links work entirely with native HTML anchor links and CSS. The browser handles scrolling to the target and moving focus when the link is activated. Zero JavaScript required.

// 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)
Try it on this page This very page has a working skip link. Click the browser address bar and press Tab — you'll see "Skip to main content" appear at the top left. Press Enter to activate it, then Tab again to confirm focus moved past the navigation.

// 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

Using 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.
Placing the skip link after the navigation If the skip link appears in the DOM after the nav links, the user has already tabbed through all the content they wanted to skip by the time they reach it. The skip link must be the first focusable element in the document — before the logo, before the nav, before everything.
Target element not receiving focus If the target element (e.g., <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.
Broken or missing 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.
Skip link only visible to screen readers (never shown visually) Some implementations keep the skip link permanently visually hidden using .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.
Too many skip links Adding skip links for every section ("Skip to search," "Skip to sidebar," "Skip to footer") clutters the keyboard experience. One skip link to the main content is sufficient for most sites. Add a second only if the page has a genuinely distinct, frequently accessed section like a search form.

// 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
The verdict Skip links need zero ARIA. A native anchor link gives you everything: semantics, keyboard interaction, focus management, and scrolling — all without JavaScript. This is one of the purest examples of "native HTML first" on the entire site.