Search topics
Sticky headers and banners that hide keyboard focus
The browser treats a link under a sticky header or fixed cookie banner as already in view, so it never scrolls it out from under the bar.
The problem
Keyboard users tab to a link they cannot see, because a header or banner is drawn over it.
A gardening site, Garden Notes, lists its articles as links: "Spring planting guide", "Choosing a compost bin", "Pruning roses" and more. The site header, with the logo and menu, sticks to the top of the window. A cookie banner is fixed to the bottom until someone answers it. Press Tab down the list: the next two links sit behind the cookie banner, and focus moves onto them without the page moving. Focus is on a link nobody can see. Press Shift + Tab back up and the same happens under the header.
When focus moves, the browser scrolls only if the element is outside the scroll area. A link behind a sticky header or fixed banner is inside that area, so as far as the browser knows it is already in view, and the page does not move. When the browser does scroll, how far depends on the browser: some centre the element, others stop with it flush against the edge, under the bar. Either way, nothing tells the browser that the bars cover part of the window. Success criterion 2.4.11 requires that a focused control is not entirely hidden by content the author put there. Here it is entirely hidden, in both directions.
Sighted keyboard users, including people who use switch access or voice control that moves focus, lose track of where they are and may press Enter on a link they never saw. People who zoom in are hit hardest: at 200% the header and banner can fill a third of the window, so more of the page sits under them.
The markup is correct: the links have names, the order is right, and a focus style is defined. Hidden focus only exists after a scroll, at a given window size, with a banner that may only show on a first visit. Most scanners load the page once at the top and never tab through it. A generated layout often adds position: sticky without anything that reserves its space.
Try it
Press Tab down the list, then Shift + Tab back up.
Watch each article link as it gets focus. Can you always see it, or does it slip under the header or the cookie banner?
Broken Can you fix this?
A recreated demo. The broken version is intentionally inaccessible.
The fix
Reserve the bars' height with scroll-padding, or keep the bars out of the area that scrolls.
- 1The focused control is never entirely behind a sticky or fixed bar, in either Tab direction.
- 2Space reserved for a bar comes from the bar's own height, so the two stay in step.
- 3Check with every banner, chat button and zoom level you ship.
.site-header { position: sticky; top: 0; height: 4rem;}.cookie-banner { position: fixed; inset-inline: 0; bottom: 0; height: 5rem;}The page scrolls under both bars, and nothing tells the browser that the top 4rem and bottom 5rem of the window are covered. Every link it scrolls into view by focus stops under one of them.
scroll-padding tells the browser which part of the window is really visible, so focus scrolling, anchor links and scrollIntoView stop clear of the bars. The heights and the padding come from the same tokens, so they cannot drift apart. This works only while the bars keep those heights: if the header's text wraps at a narrow width or when enlarged and the header grows, the padding must grow with it. Use rem heights that scale with the text, or update the variables from a ResizeObserver. Set scroll-padding on the element that actually scrolls; if the content scrolls inside a container, put it there instead of on html.
Both pass. scroll-padding is the smaller change and keeps the normal page scroll, including the mobile address bar that hides as you scroll, but it depends on the reserved space matching the bars. The grid layout needs no measuring, but it moves scrolling from the page into main, which changes how pull-to-refresh, scroll restoration and some analytics scripts behave. Choose scroll-padding for an existing site where the bars have fixed heights; choose the layout when you are building the page shell or the bars change height with their content.
Count every layer that floats over the page: the header, cookie and promotion banners, chat launchers and "back to top" buttons. Each one needs its space reserved or a place outside the scroll area. A floating button in a corner can hide a small control entirely even when the bars are handled.
A banner people can dismiss still fails while it is showing, so reserve its space until it is gone. When zoomed in, very tall bars can leave almost no room for content; consider letting the header scroll away at small heights, for example with a @media (max-height: 30rem) rule that turns position: sticky off. Partly covered focus passes 2.4.11 at level AA, but 2.4.12 at AAA asks for no covering at all, and partly hidden focus is still hard to read: aim for the whole control to show.
Verify the fix
4 checks, no mouse.
Test with: Keyboard, Zoom
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the header, banners or page layout change.
Limits
This is a starting pattern, not a guarantee for every application. Browsers apply scroll-padding to focus scrolling in their current versions, but check yours, and check scroll containers inside the page, such as carousels and side panels, which each need their own padding. Token names in the snippets are placeholders for your own design tokens.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.