Skip-links

First posted on October 29, 2025 — last updated on November 8, 2025

Let’s start with one of the simplest yet most important components: the Skip-links.

Skip-links are hidden links at the top of a webpage that let users jump directly to a specific section (usually the main content) of the page.

They become visible when focused (via keyboard navigation) and help users skip repetitive elements like navigation menus.

Why do we need them?

Imagine browsing a site using only your keyboard. Every time you load a new page, you’d have to tab through every navigation link before reaching the actual content.

Ugh, that gets tiring quickly.

Skip-links solve this problem by letting users jump straight to what matters with (almost) a single keystroke. That’s not just accessibility: it’s good UX, plain and simple.

How to build them

Start with a simple anchor inside a wrapper (the wrapper will help you with the styling and also come in handy later):

<div class="skip-links">
  <a>Skip to main content</a>
</div>

Be sure to place the Skip-links before the site navigation to not defeat its purpose. Then simply reference the id of the target element (typically the <main> landmark) in the link’s href:

<div class="skip-links">
  <a href="#main">Skip to main content</a>
</div>
...
<main id="main">...</main>

And voilà! That’s already a functional Skip-links: no JavaScript required!

Managing visibility

Now, we want the Skip-links to remain hidden until they receive focus, so we would do something like this:

.skip-links {
  position: absolute;
  transform: translateY(-100%);
}

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

This keeps the link off-screen by default and brings it into view when focused.

To make the reveal a bit more elegant, we can add a transition. Just be mindful and respect users’ preferences by wrapping it in a media query to ensure animations are disabled for those who prefer reduced motion.

@media (prefers-reduced-motion: no-preference) {
  .skip-links {
    transition: transform 0.3s;
  }
}

Where’s the focus-indicator?

By default, the <main> element isn’t focusable, so while the browser scrolls to it and the keyboard focus moves there, the focus-indicator doesn’t show up. This can leave the user disoriented and violates the WCAG 2.2 Success Criterion 2.4.7: Focus Visible (opens in a new window).

To fix that, simply add tabindex="-1" to your target:

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

This allows the focus-indicator to move to <main> when using the Skip-links, without adding it to the regular tab order. Neat!

Optional: add some more

If your <footer> includes interactive elements, consider adding a Skip-link pointing to it as well:

<div class="skip-links">
  <a href="#main">Skip to main content</a>
  <a href="#footer">Skip to footer</a>
</div>

And while you’re at it, why not use proper list semantics and slap an aria-label on it too?

<ul class="skip-links" aria-label="Skip-links">
  <li><a href="#main">Skip to main content</a></li>
  <li><a href="#footer">Skip to footer</a></li>
</ul>

We are almost done, but wait!

Typically, only the focused link is visible. So, how do we achieve that? We can leverage a classic accessibility hack: the visually-hidden class (opens in a new window).

.visually-hidden:not(:focus):not(:active) {
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

This little beauty hides elements visually while keeping them accessible to assistive technologies and keyboard users. When the hidden element receives focus, it becomes visible again thanks to the :not() part.

You just need to apply it to both anchor elements:

<ul class="skip-links" aria-label="Skip-links">
  <li><a class="visually-hidden" href="#main">Skip to main content</a></li>
  <li><a class="visually-hidden" href="#footer">Skip to footer</a></li>
</ul>

And that’s it! Now you have a simple yet accessible Skip-links component ready for your blog. Until next time, be (and code) well!

Back to top