Patterns Skip link

Skip link

A first-focusable link that lets keyboard users jump past long, repeated navigation. Cheap to add, expensive to live without.

Reviewed against the methodology checklist Updated

Try the keyboard

When to use

Always — on every page that has more than a handful of links between the top of the document and the main content. Sighted mouse users never see it. Keyboard users press Tab once and skip the lot.

The pattern

Skip link in context
Skip to main content

Tab into the demo above to reveal the skip link. Activate it and focus jumps here, past the nav.

The whole pattern is two HTML elements and a few lines of CSS:

HTML
<a href="#main" class="skip-link">Skip to main content</a>

<header>…site nav…</header>

<main id="main" tabindex="-1">

</main>

The tabindex="-1" on <main> is what lets the link actually move focus there. Without it, modern browsers move visual scroll position to the target but leave keyboard focus where it was — so the next Tab keypress dumps the user back into the nav they tried to skip.

Visible on focus, not on hover

The skip link should be invisible until a keyboard user reaches it, then unmistakable:

CSS
.skip-link {
position: absolute;
inset-inline-start: 1rem;
inset-block-start: 1rem;
padding: 0.5rem 1rem;
background: var(--color-accent);
color: var(--color-accent-fg);
text-decoration: none;
border-radius: 0.5rem;
transform: translateY(-150%);
transition: transform 150ms;
z-index: 100;
}

.skip-link:focus {
transform: translateY(0);
}

:focus (not :focus-visible) on purpose — we want the link to appear for any focus, including programmatic. The :focus-visible variant would still work for keyboard users, but :focus is more permissive and the link is harmless for mouse users to occasionally see.

Don’t display: none or visibility: hidden it — both remove the link from the focus order entirely.

Anti-pattern: hidden by display: none

The most common mistake. The link is in the markup but uses display: none “until needed”:

CSS
.skip-link {
display: none;
}
.skip-link:focus {
display: block;
}

display: none removes the element from the accessibility tree, the focus order, and the rendering tree. The :focus rule never matches, because the link can’t be focused. Tab walks straight past it as if it weren’t there.

The same trap applies to visibility: hidden and hidden.

Common variations

Checklist

Screen reader transcript

Voiced by George — reading what VoiceOver on macOS Safari announces. Other readers (NVDA, JAWS) phrase things differently; the meaning is what matters.

Screen reader announcements for each step
ListenYou doScreen reader says
Press Tab on a fresh page loadSkip to main content, link.
Press EnterMain content, region. (focus jumps past the nav)

Screen reader transcript — anti-pattern

Same interaction, but on the broken version above. Notice what the user is missing.

Screen reader announcements for each step
ListenYou doScreen reader says
Press Tab on a fresh page loadHome, link.
Press TabPosts, link.
Tab through every remaining nav link before reaching the contentSearch, link. About, link. Pricing, link…

WCAG references