Missing skip links past repeated navigation
The skip link is hidden with display: none, and the content sits in a plain div, so nothing lets people jump past the menu.
The problem
Keyboard users must Tab through the whole menu on every page before they reach the content.
A garden centre's shop has a header with its logo, twelve category links, a search field, an account link and a basket. Someone using a keyboard opens the "Spring bulbs" page and presses Tab. Focus goes to the logo, then to each category in turn, then search, account and basket. The first product is the seventeenth stop. On the next page, the same sixteen stops come first again.
The theme has a skip link, but its stylesheet hides it with display: none so that it never shows. That also removes it from the tab order, so nobody can use it. The content sits in a div, the header in another div, and the first heading is styled text in a p. There is no landmark or heading to jump to with a screen reader either. WCAG 2.4.1 asks for some mechanism to bypass blocks repeated on every page, and this page offers none.
Keyboard users and switch users, who move one stop at a time, pay the cost of the header on every page. For a switch user each stop can take seconds. Screen reader users can usually jump by landmark or heading, but only if the page has them. People using screen magnifiers lose their place as focus travels across a wide header.
A scanner finds the skip link in the markup and may count it as present, even though display: none takes it out of the tab order. It rarely checks that the target id exists, or that focus really moves there rather than only scrolling. A generated fix often adds <a href="#main"> and forgets to give any element id="main". Single-page apps can break a working skip link by intercepting hash links in the router.
Try it
Press Tab until you reach the first product.
Count the stops before the content. On the fixed side, press Enter on the first stop.
Spring bulbs
Tab stops before the content: 0
Landmarks and headings a screen reader lists:
(none)
A recreated demo. The broken version is intentionally inaccessible.
The fix
Show the skip link on focus and point it at the main element, or mark up the page with landmarks and a heading.
- 1Content starts in a main element, so there is somewhere to jump to.
- 2A skip link is the first Tab stop, shown on focus, and moves focus to its target.
- 3Hide a skip link only visually, never with display: none.
html
<a class="skip-link" href="#content">Skip to content</a><div class="header"> <a href="/">Fernway Garden</a> <ul class="menu">…12 category links…</ul> <input type="search" aria-label="Search"> <a href="/account">Account</a> <a href="/basket">Basket</a></div><div class="content"> <p class="title">Spring bulbs</p> …</div>css
.skip-link { display: none; }display: none removes the link from the tab order, and #content matches no id. The page has no landmarks and no heading, so there is no way past the header.
The link is the first Tab stop and moves into view when it has focus. Its target exists, and tabindex="-1" lets main take focus, so the next Tab starts inside the content in every browser rather than back at the header. Sighted keyboard users can see it and use it.
Both pass 2.4.1. The skip link is the only one that helps a sighted person on a keyboard or a switch, and it costs one link and a few lines of CSS. Landmarks and a heading cost nothing to keep, help every screen reader user and also meet 1.3.1, but leave the Tab journey as long as before. Use the skip link and add the landmarks too; if only one is possible in this release, start with the skip link.
Hide the link with a transform, a clip or an off-screen position, never with display: none, visibility: hidden or hidden, and remove the hiding on :focus. Give it the same focus ring as other links and a contrast that passes on the header.
tabindex="-1" on main lets some browsers draw a focus ring around the whole content. That is acceptable, and it shows where focus went; if you remove it, use main:focus:not(:focus-visible) rather than outline: none. Don't make main a normal Tab stop with tabindex="0".
A sticky header can cover the top of main after the jump. Add scroll-margin-top to the target, the height of the header. In a single-page app, check that the router does not swallow #main links; if it does, handle the click and call focus() on the target. Keep the skip link in the shared layout, so every page has it and its target.
One skip link to the main content is enough for most sites. Add more ("Skip to filters") only when another long block repeats too.
Verify the fix
5 checks, no mouse.
Test with: Keyboard, Screen reader, Zoom
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the header or the layout changes.
Limits
This is a starting pattern, not a guarantee for every application. Pages with several repeated blocks, iframes or app shells may need more than one way past them. Test the complete journey on your real pages.
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.