Skip to main content
Skip links are one of the most important accessibility features for keyboard users. They allow users to bypass repetitive navigation and jump directly to the main content.
Keyboard users must tab through every focusable element to navigate a page. Without skip links, they must tab through the entire navigation menu on every page load before reaching the main content.
For a site with 20 navigation links, a keyboard user would need to press Tab 20 times on every page just to reach the content. Skip links solve this problem. Here’s how the course website implements a skip link:
<header class="sticky top-0 z-10 py-3">
  <a href="#main-content" class="skip-link">Skip to content</a>

  <nav class="container mx-auto flex items-center justify-between gap-4">
    <!-- Navigation content -->
  </nav>
</header>

<main id="main-content" tabindex="-1">
  <!-- Main page content -->
</main>
1

Create the skip link

Place a skip link as the first focusable element in your <header>:
<a href="#main-content" class="skip-link">Skip to content</a>
2

Add ID to main content

Give your <main> element an id that matches the skip link href:
<main id="main-content" tabindex="-1">
  <!-- Content -->
</main>
The tabindex="-1" allows the main element to receive focus programmatically without adding it to the natural tab order.
3

Style the skip link

Make the skip link visible when focused. It can be hidden by default but must appear when keyboard users tab to it:
.skip-link {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.skip-link:focus {
  position: static;
  width: auto;
  height: auto;
  overflow: visible;
  /* Add visible styling */
  padding: 0.5rem 1rem;
  background: #3B82F6;
  color: white;
  text-decoration: none;
}

Accessible navigation menus

Basic navigation structure

Here’s the navigation structure from the course website:
index.html
<header class="sticky top-0 z-10 py-3">
  <a href="#main-content" class="skip-link">Skip to content</a>

  <nav
    class="container mx-auto flex items-center justify-between gap-4 max-md:flex-wrap [&_a]:p-2"
  >
    <h1 class="max-md:mx-auto">
      <a class="flex items-center gap-2 text-2xl" href="/" aria-current="page">
        <img
          src="../../public/tdl-logo.jpg"
          width="30"
          height="30"
          class="inline rounded-full shadow-md"
          alt="TestDevLab"
        />
        Web A11y for Devs
      </a>
    </h1>

    <ul class="flex flex-wrap gap-2 *:min-w-fit max-md:w-full max-md:justify-between">
      <li><a href="./introduction">Introduction</a></li>
    </ul>
  </nav>
</header>

Key accessibility features

Semantic nav element

Using <nav> tells screen readers this is a navigation region, allowing users to jump directly to it.

List structure

Using <ul> and <li> provides semantic structure and count information (“list, 1 item”).

Descriptive link text

Links have clear, descriptive text like “Introduction” instead of generic “click here”.

Current page indication

The aria-current="page" attribute marks the current page for screen reader users.

Current page indication

The aria-current attribute is crucial for helping users understand their location within the site.

Using aria-current

<ul>
  <li><a href="./introduction" aria-current="page">Introduction</a></li>
  <li><a href="./components">Components</a></li>
  <li><a href="./forms">Forms</a></li>
</ul>
When a screen reader encounters aria-current="page", it announces something like:
“Introduction, link, current page”
This immediately tells users which page they’re currently viewing.

Values for aria-current

Indicates the current page within a set of pages.
<a href="/introduction" aria-current="page">Introduction</a>
Most common use case for navigation menus.
Indicates the current step in a multi-step process.
<li aria-current="step">Step 2: Payment</li>
Useful for checkout flows or wizards.
Indicates the current location within an environment or context.
<a href="#section-2" aria-current="location">Section 2</a>
Good for table of contents or in-page navigation.
Indicates the current date within a calendar or date picker.
<button aria-current="date">March 4, 2026</button>
Indicates the current time within a time picker.
<button aria-current="time">10:30 AM</button>
Generic current indicator when no specific type applies.
<div aria-current="true">Current item</div>
Don’t use aria-current="false" - simply omit the attribute on non-current items.

Keyboard navigation best practices

Tab order matters

Ensure your navigation follows a logical tab order:
1

Skip link first

The skip link should be the first focusable element:
<header>
  <a href="#main-content" class="skip-link">Skip to content</a>
  <!-- Then navigation -->
</header>
2

Logo and navigation

Then users should be able to tab through the logo and navigation links in a logical order:
<nav>
  <h1><a href="/">Logo</a></h1>
  <ul>
    <li><a href="/introduction">Introduction</a></li>
    <li><a href="/course-structure">Course Structure</a></li>
  </ul>
</nav>
3

Main content next

After navigation, focus should move into the main content area.
Never use tabindex values greater than 0. This disrupts the natural tab order and creates a confusing experience.
<!-- Bad - don't do this -->
<a href="/" tabindex="1">Link 1</a>
<a href="/" tabindex="2">Link 2</a>

Focus indicators

All navigation links must have visible focus indicators. From the course material:
“Any interactive element must have a focus indicator to let (keyboard-only) users know where they are on the page.”
You don’t have to use the default outline - there are many creative ways to show focus:
/* Background change on focus */
nav a:focus {
  background-color: #3B82F6;
  color: white;
  outline: none;
}

/* Box shadow focus indicator */
nav a:focus {
  box-shadow: 0 0 0 3px #3B82F6;
  outline: none;
}

/* Transform on focus */
nav a:focus {
  transform: scale(1.05);
  outline: 2px solid #3B82F6;
  outline-offset: 2px;
}
If you remove the default outline with outline: none, you MUST provide an alternative visible focus indicator. Never leave interactive elements without a focus indicator.

Multiple navigation regions

When you have multiple <nav> elements on a page, label them to help screen reader users distinguish between them:
<header>
  <nav aria-label="Primary navigation">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/introduction">Introduction</a></li>
      <li><a href="/course-structure">Course Structure</a></li>
    </ul>
  </nav>
</header>

<main>
  <!-- Content -->
</main>

<footer>
  <nav aria-label="Footer navigation">
    <ul>
      <li><a href="/privacy">Privacy</a></li>
      <li><a href="/terms">Terms</a></li>
    </ul>
  </nav>
</footer>

Mobile navigation considerations

For responsive navigation menus that toggle visibility:

Toggle button accessibility

<button
  aria-expanded="false"
  aria-controls="mobile-menu"
  class="menu-toggle"
>
  <span class="sr-only">Open menu</span>
  <!-- Hamburger icon -->
</button>

<nav id="mobile-menu" hidden>
  <ul>
    <li><a href="/">Home</a></li>
    <!-- More links -->
  </ul>
</nav>
1

Use aria-expanded

Indicates whether the menu is expanded or collapsed:
toggleButton.addEventListener('click', () => {
  const expanded = toggleButton.getAttribute('aria-expanded') === 'true';
  toggleButton.setAttribute('aria-expanded', !expanded);
  menu.hidden = expanded;
});
2

Use aria-controls

Links the button to the menu it controls:
<button aria-controls="mobile-menu">Menu</button>
<nav id="mobile-menu"><!-- Links --></nav>
3

Toggle hidden attribute

Use the hidden attribute to hide/show the menu:
menu.hidden = false; // Show
menu.hidden = true;  // Hide
4

Manage focus

When the menu opens, optionally move focus to the first link. When it closes, return focus to the toggle button:
if (!expanded) {
  // Opening menu
  menu.querySelector('a').focus();
} else {
  // Closing menu
  toggleButton.focus();
}

Real-world example

The course website demonstrates these patterns throughout its navigation structure:
<body class="min-h-dvh">
  <header class="sticky top-0 z-10 py-3">
    <a href="#main-content" class="skip-link">Skip to content</a>

    <nav class="container mx-auto flex items-center justify-between gap-4">
      <h1 class="max-md:mx-auto">
        <a class="flex items-center gap-2 text-2xl" href="/">
          <img
            src="../../public/tdl-logo.jpg"
            width="30"
            height="30"
            class="inline rounded-full shadow-md"
            alt="TestDevLab"
          />
          Web A11y for Devs
        </a>
      </h1>

      <ul class="flex flex-wrap gap-2">
        <li><a href="./introduction" aria-current="page">Introduction</a></li>
      </ul>
    </nav>
  </header>

  <main id="main-content" tabindex="-1">
    <!-- Main content here -->
  </main>

  <footer class="py-3 text-center font-semibold">
    <p>Web A11y for Devs, <span id="current-year"></span></p>
  </footer>
</body>
Notice:
  • Skip link at the top
  • Semantic <nav> element
  • List structure for navigation items
  • aria-current="page" on current page link
  • Proper landmark elements (header, nav, main, footer)
  • Descriptive link text
  • Logo with alt text on image

Best practices checklist

Use <nav>, <ul>, and <li> for navigation menus, not generic divs.
Use aria-current="page" on the current page link in navigation.
Ensure all navigation links have clear, visible focus indicators.
Never use tabindex > 0. Let the DOM order define the tab order.
Use aria-label to distinguish between multiple navigation regions.
Use aria-expanded and aria-controls for mobile menu toggles.

Resources

Build docs developers (and LLMs) love