DEV Community

AgentKit
AgentKit

Posted on • Originally published at blog.a11yfix.dev

Sticky Header Accessibility: The Skip Link, Focus, and Reflow Problems Most Sites Miss

A sticky header looks great in a design mock-up. It also breaks accessibility in three ways that most automated scanners do not catch and most QA passes do not test. If your site has a sticky navigation that stays at the top as users scroll, this article is for you.

By the end you will know exactly which WCAG 2.2 criteria your sticky header probably fails, how to test for each one in under five minutes, and how to fix the issues without ditching the sticky design that your designer or client is attached to.

Why sticky headers cause accessibility problems

Sticky headers (sometimes called "fixed" or "pinned" headers) use CSS position: fixed or position: sticky to keep navigation visible as users scroll the page. They are popular for one reason: convenience. Users do not have to scroll back to the top to use the menu.

The trouble is that a sticky element occupies real estate at the top of the viewport for the entire session. That breaks four assumptions the accessibility specifications were built around:

  1. Keyboard focus is fully visible. Browsers scroll the page so that the focused element is on screen, but they do not know your sticky header exists, so they sometimes scroll the focused element to a position where the header overlaps it.
  2. Skip links do what they say. A "Skip to content" link adjusts the URL fragment to #main-content and the browser jumps to that element. If your sticky header is 80 pixels tall, the user lands inside or behind the header rather than at the top of the main content.
  3. Content reflows cleanly at 320 pixels. WCAG 1.4.10 requires that content stay readable when zoomed to 400% on a 1280-pixel viewport, which is equivalent to a 320-pixel-wide window. A sticky header that occupies 60% of the viewport at that zoom level leaves almost nothing for the actual content.
  4. Users with vestibular disorders are not given motion they cannot opt out of. Some sticky headers slide in and out as the user scrolls up and down, triggering nausea and disorientation in people with vestibular sensitivities.

The good news is that all four problems have well-defined fixes that take less than an hour to implement on most sites. The rest of this article walks through each one.

Problem 1: Focus hidden behind the sticky header (WCAG 2.4.11)

This is the most common sticky-header bug, and it is severe enough that the W3C added a brand-new WCAG 2.2 success criterion specifically to address it.

What the criterion says

WCAG 2.4.11 Focus Not Obscured (Minimum) is a Level AA criterion added in WCAG 2.2 (October 2023). It states that when an element receives keyboard focus, it must not be entirely hidden by author-created content. There is also a Level AAA version (2.4.12) that requires the focused element to not be partially hidden either.

Why sticky headers fail it

Imagine a user on your site is tabbing through the navigation menu. They reach the last menu item and press Tab again. Focus moves to the first link inside the main content area, which is currently sitting just below the sticky header in the visible viewport. The browser sees that the focused element is on screen and does nothing.

Now imagine the user has scrolled halfway down the page and presses Shift+Tab to move focus backward. Focus jumps to a link near the top of the page, the browser scrolls the page to bring that link into view, and the link lands exactly under the sticky header. The user cannot see the focus indicator. They have no idea where they are in the tab order.

This is a Level AA failure under WCAG 2.4.11, and it affects keyboard users including people with motor disabilities, screen-reader users, and power users who navigate with the keyboard for speed.

How to test for it in five minutes

  1. Open your site on a desktop browser.
  2. Press Tab repeatedly. Watch for the focus outline.
  3. Now scroll down halfway with your mouse, then press Shift+Tab a few times.
  4. Did focus ever land in a position where the focus outline was partially or fully behind the sticky header?

If yes, you have the bug.

The fix: scroll-padding-top

Modern browsers support a CSS property called scroll-padding-top, which tells the browser how much vertical space to reserve at the top of the viewport when scrolling to bring something into view. Set it to the height of your sticky header.

html {
  scroll-padding-top: 80px;
}
Enter fullscreen mode Exit fullscreen mode

If your sticky header has a different height on mobile and desktop, use a CSS variable:

:root {
  --sticky-header-height: 80px;
}

@media (max-width: 640px) {
  :root {
    --sticky-header-height: 56px;
  }
}

html {
  scroll-padding-top: var(--sticky-header-height);
}
Enter fullscreen mode Exit fullscreen mode

That single property fixes both keyboard scroll and anchor-link scroll. Test it by repeating the keyboard test above.

Problem 2: Broken skip links (WCAG 2.4.1)

A skip link is the small "Skip to main content" link that appears as the first focusable element on the page. Keyboard users press Tab once, see the skip link appear, press Enter, and jump directly to the page content without tabbing through every navigation item.

Why sticky headers break skip links

When the user activates the skip link, the browser changes the URL hash to #main-content and scrolls so that the element with that ID is at the top of the viewport. With a sticky header in place, "the top of the viewport" is hidden behind 60-80 pixels of navigation. The user has effectively skipped to the navigation, not past it.

Worse, in some browsers the focus does not actually move to the target element on hash change. The user sees the page scroll but their next Tab press goes back to the link after the skip link, not into the main content.

The fix: scroll-padding-top + tabindex="-1" on the target

scroll-padding-top from the previous fix handles the visual scrolling. To make sure focus actually lands inside the main content, add tabindex="-1" to the target element so it can receive programmatic focus:

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

<header class="sticky-header">...</header>

<main id="main-content" tabindex="-1">
  <h1>Page title</h1>
  ...
</main>
Enter fullscreen mode Exit fullscreen mode

The tabindex="-1" makes the <main> element programmatically focusable without putting it in the tab order. When the user activates the skip link, browsers that move focus on hash change will land inside the main element. Users on browsers that do not move focus automatically still benefit from the visible scroll.

Style your skip link to be visually hidden until focused:

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 999;
  padding: 1rem;
  background: #ffffff;
  color: #000000;
  border: 2px solid #000000;
}

.skip-link:focus {
  left: 1rem;
  top: 1rem;
}
Enter fullscreen mode Exit fullscreen mode

Test in five minutes

  1. Load your home page.
  2. Press Tab once. The skip link should appear in the top-left.
  3. Press Enter.
  4. The page should scroll so the H1 of the main content is fully visible (not behind the header).
  5. Press Tab again. Focus should land on the first interactive element inside the main content, not back at the navigation.

If any of those steps fail, your skip link is not actually doing its job.

Problem 3: Reflow at 320 pixels (WCAG 1.4.10)

WCAG 1.4.10 Reflow is a Level AA criterion that requires content to be presented without horizontal scrolling and without loss of information when the viewport is 320 pixels wide. The most common way to test this is to load your page in a desktop browser at 1280 pixels and zoom to 400%. The visible area becomes 320 pixels wide.

Why sticky headers fail reflow

At 400% zoom on a 1280-pixel desktop, you have 320 pixels of horizontal space and roughly 200 pixels of vertical space. If your sticky header is normally 80 pixels tall, at 400% zoom it can balloon to 200 or 300 pixels (because the logo and menu text are also zoomed). The header now occupies most of the viewport, leaving the user with a sliver of content below it.

Some sites compound this by using two sticky elements: a header at the top and a scroll-progress bar or call-to-action banner. At 400% zoom, the two together can take up the entire visible viewport.

The fix: reduce sticky header height at smaller viewports

The simplest fix is to make the header non-sticky below a certain viewport height, or to dramatically shrink it. Both work.

.sticky-header {
  position: fixed;
  top: 0;
  width: 100%;
  height: 80px;
}

/* When zoomed beyond ~250%, vh < height threshold */
@media (max-height: 400px) {
  .sticky-header {
    position: static; /* stop being sticky */
    height: auto;
  }
}

/* Or shrink it dramatically */
@media (max-height: 400px) {
  .sticky-header {
    height: 40px;
    font-size: 0.75rem;
  }
  .sticky-header .logo {
    display: none;
  }
}
Enter fullscreen mode Exit fullscreen mode

Either approach passes WCAG 1.4.10. Test by zooming your browser to 400% and confirming you can read at least three lines of body content below the header.

Problem 4: Slide-in animations and vestibular disorders (WCAG 2.3.3)

Some sticky headers do not stay at the top permanently. They hide on scroll-down and slide in on scroll-up. The slide animation usually lasts 200-400 milliseconds and triggers every time scroll direction changes.

For users with vestibular disorders (which the CDC estimates affect 35% of adults over 40), a header that slides in and out repeatedly is nauseating. WCAG 2.3.3 Animation from Interactions is a Level AAA criterion that requires authors to provide a way to disable non-essential motion triggered by user interaction. While this is AAA (not the AA target most legal frameworks require), the broader WCAG 2.2 best-practice guidance also points at the prefers-reduced-motion media query as a Level AA way to handle this.

The fix: respect prefers-reduced-motion

Wrap the slide animation in a prefers-reduced-motion media query so that users who have set their operating system to reduce motion get the header without the animation:

.sticky-header {
  transition: transform 300ms ease;
}

.sticky-header.is-hidden {
  transform: translateY(-100%);
}

@media (prefers-reduced-motion: reduce) {
  .sticky-header {
    transition: none;
  }
  .sticky-header.is-hidden {
    transform: none; /* keep it visible, no animation */
  }
}
Enter fullscreen mode Exit fullscreen mode

The prefers-reduced-motion setting is exposed by macOS, Windows, iOS, and Android system preferences. About 25% of mobile users have it enabled by default, which is enough to make this a serious accessibility consideration even before legal compliance.

Putting it all together: the five-minute sticky-header audit

If you have a sticky header on your site right now, set a five-minute timer and run through this checklist:

  1. Tab and Shift+Tab through the page. Does the focus indicator ever hide behind the header? If yes, add scroll-padding-top to the html element.
  2. Activate the skip link. Does focus land inside the main content (not back at the nav)? If no, add tabindex="-1" to your main landmark.
  3. Zoom to 400%. Can you read at least three lines of body content under the header? If no, shrink or unstick the header at small viewport heights.
  4. Set your OS to "reduce motion" and reload. Does the header still slide in and out? If yes, wrap your animation in a prefers-reduced-motion: reduce media query.

These four fixes cover the realistic scope of sticky-header accessibility issues. None of them require redesigning the header. None of them ask your client or designer to remove the sticky behavior. They just make the existing design work for keyboard users, screen-reader users, and people with vestibular disorders.

What about position: sticky vs. position: fixed?

Both have the same accessibility implications. position: sticky only sticks once the element scrolls past a threshold, but once stuck it behaves identically to position: fixed for the purposes of keyboard focus, skip links, and reflow. The fixes in this article apply to both.

Common questions

Should I just remove the sticky header to fix all this?

You can, but you do not have to. A sticky header that respects scroll-padding-top, has a working skip link, shrinks at 320 pixels, and respects prefers-reduced-motion is fully compliant with WCAG 2.2 AA. Designers love sticky headers because they reduce navigation friction, and users generally prefer them too. Fix the bugs rather than removing the feature.

My header is sticky only on desktop, not mobile. Do I still need to test?

Yes. Test on the breakpoint where the sticky behavior is active. Run the keyboard tests on desktop. The mobile-specific tests (320px reflow) become less critical because the header is not sticky there anyway, but you should still confirm by manually toggling DevTools' device emulation.

My CMS does not let me edit the HTML to add tabindex="-1".

Most CMSs offer a custom-code field where you can add a JavaScript snippet that runs on page load. Use this snippet:

document.addEventListener('DOMContentLoaded', () => {
  const main = document.querySelector('main');
  if (main && !main.hasAttribute('tabindex')) {
    main.setAttribute('tabindex', '-1');
  }
});
Enter fullscreen mode Exit fullscreen mode

That ensures every page on your site has a properly focusable main landmark.

Do I need to fix WCAG 2.4.12 (Focus Not Obscured Enhanced) too?

Only if you are targeting Level AAA compliance. For ADA, EAA, and most other legal compliance regimes, Level AA (2.4.11) is sufficient. AAA is a stretch goal, not a legal requirement.

The bottom line

Sticky headers are not inherently inaccessible. They become inaccessible when the design treats the viewport as if the header is not there. Adding scroll-padding-top, fixing the skip link target, reducing header height at 320px, and respecting prefers-reduced-motion is roughly an hour of work for the average website and addresses every realistic sticky-header WCAG failure mode.

If you have not run the five-minute audit on your own site, do it now. The fixes are small. The compliance and usability gains are large.

We're building a simple accessibility checker for non-developers -- no DevTools, no jargon. Join our waitlist to get early access.

Related Reading

Top comments (0)