Build Keyboard‑Friendly Anchor Navigation with Pure CSS and ARIA

If you’ve ever tried to tab through a page and found yourself stuck in a dead‑end of links, you know why this matters. Good keyboard navigation isn’t a nice‑to‑have; it’s a must‑have for anyone who cares about accessibility, SEO, and plain old user happiness. In this post I’ll walk you through a simple way to make anchor navigation work smoothly using only CSS and a sprinkle of ARIA attributes. No JavaScript, no heavy libraries—just the tools we already have in the browser.

Why Pure CSS?

When I first started tinkering with navigation menus, I reached for JavaScript every time I wanted a smooth scroll or a hidden submenu. It worked, but the code grew fast and I kept worrying about people who have JavaScript turned off or who use screen readers that don’t like hidden DOM nodes. Pure CSS keeps the markup clean, reduces load time, and plays nicely with assistive technology when we add the right ARIA roles.

The Basics: Anchors, IDs, and Focus

What is an anchor?

An anchor is a link that points to a specific part of the same page. In HTML you write it like this:

<a href="#section‑one">Go to Section One</a>

The #section‑one part tells the browser to look for an element with the matching id attribute:

<h2 id="section‑one">Section One</h2>

When the link is clicked, the browser jumps to that heading. For keyboard users, the same thing happens when they press Enter while the link is focused.

Making the target focusable

By default, headings are not focusable with the keyboard. That means a screen reader user can hear the jump, but they can’t keep the focus there to continue reading. The fix is simple: add a tabindex="-1" to the target element. This makes it focusable programmatically without adding it to the normal tab order.

<h2 id="section‑one" tabindex="-1">Section One</h2>

Now when the link is activated, the browser moves focus to the heading, and the user stays in the flow.

Adding ARIA for Clarity

ARIA (Accessible Rich Internet Applications) attributes give extra hints to assistive tech. For a navigation block, we should use role="navigation" and give it a clear label.

<nav role="navigation" aria-label="Main page sections">
  <ul>
    <li><a href="#section‑one">Section One</a></li>
    <li><a href="#section‑two">Section Two</a></li>
    <li><a href="#section‑three">Section Three</a></li>
  </ul>
</nav>

The aria-label tells a screen reader what this list of links is for, instead of just saying “list of links”.

Styling the Focus State

A big part of keyboard friendliness is visual feedback. Users need to see where the focus is. Browsers give a default outline, but it’s often too thin or the color clashes with our design. We can style it with CSS:

a:focus {
  outline: 3px solid #ff9800; /* bright orange for visibility */
  outline-offset: 2px;
}

If you’re using a dark theme, pick a color that stands out against the background. The key is to keep the outline thick enough to be seen by people with low vision.

Smooth Scrolling with CSS

Scrolling instantly to a heading can feel jarring. Modern browsers support smooth scrolling via the scroll-behavior property. Add it to the html element:

html {
  scroll-behavior: smooth;
}

Now every time a user clicks an anchor link—or activates it with the keyboard—the page glides to the target. No JavaScript needed.

Putting It All Together

Below is a minimal example that combines everything we’ve talked about. Feel free to copy it into a new file and open it in your browser.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Toggle Anchors Demo</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      line-height: 1.6;
      margin: 2rem;
    }
    nav {
      margin-bottom: 2rem;
    }
    a {
      color: #0066cc;
      text-decoration: none;
    }
    a:hover,
    a:focus {
      text-decoration: underline;
    }
    a:focus {
      outline: 3px solid #ff9800;
      outline-offset: 2px;
    }
    html {
      scroll-behavior: smooth;
    }
    section {
      padding-top: 4rem; /* space for fixed header if you have one */
      margin-bottom: 4rem;
    }
  </style>
</head>
<body>

  <nav role="navigation" aria-label="Main page sections">
    <ul>
      <li><a href="#section‑one">Section One</a></li>
      <li><a href="#section‑two">Section Two</a></li>
      <li><a href="#section‑three">Section Three</a></li>
    </ul>
  </nav>

  <section id="section‑one" tabindex="-1">
    <h2>Section One</h2>
    <p>Welcome to the first part of the page. Notice how the focus lands here when you click the link above.</p>
  </section>

  <section id="section‑two" tabindex="-1">
    <h2>Section Two</h2>
    <p>This is the second section. The same rules apply—focus moves here, and the outline shows up.</p>
  </section>

  <section id="section‑three" tabindex="-1">
    <h2>Section Three</h2>
    <p>Last stop! If you can get here without using a mouse, you’ve built a keyboard‑friendly navigation.</p>
  </section>

</body>
</html>

What to Watch Out For

  • Skip Links – If your page has a fixed header, the target heading might end up hidden behind it. Adding a top padding or using a small offset with CSS can solve this.
  • Focus Ring Visibility – Some browsers let users turn off focus outlines in their settings. Respect that choice; don’t try to hide the outline completely.
  • Screen Reader Announcements – When focus moves to a heading, most screen readers announce the heading level and text automatically. Test with VoiceOver or NVDA to be sure.

A Little Story from the Field

A few months back I was helping a client redesign their product documentation. Their old site used a JavaScript carousel for the table of contents, and keyboard users were getting stuck on the “next” button with no way to jump to a specific chapter. I stripped out the script, added a plain list of anchor links, and applied the ARIA and focus tricks we just covered. The client’s support tickets dropped dramatically—people stopped emailing “I can’t get to the right section with my keyboard.” It was a reminder that sometimes the simplest solution is the most powerful.

TL;DR

  • Use plain anchor links (href="#id").
  • Add tabindex="-1" to each target element so focus can land there.
  • Wrap the list in a <nav> with role="navigation" and a clear aria-label.
  • Style a:focus with a visible outline.
  • Enable smooth scrolling with html { scroll-behavior: smooth; }.

With these steps, you’ve built a navigation that works for mouse users, keyboard users, and screen reader users alike—all without a single line of JavaScript. That’s the kind of clean, inclusive design I love to write about on Toggle Anchors.

Reactions