A boy's legs skip across the numbered squares of a pink and blue hopscotch board

3 common skip link problems and how to fix them

Skip links enable keyboard and screen reader users to circumvent or proceed directly to sections of content on a web page. The most common use of a skip link is to bypass a site’s header navigation, which typically appears at the beginning of every page, and immediately advance focus to the page’s main content. Since the justification for skip links and their implementation is thoroughly documented, the purpose of this article is to describe and address some common errors with skip links. According to the 2023 WebAIM Million Report, only 17% of  homepages on the top one million websites have skip links. Of the ones that do, one in six of their skip links are broken or inaccessible. So it seems there’s some work to do.

The target element is missing

A skip link typically takes the form of an anchor tag with a target on the same page, like this.

<a href="main-content'>Skip to Main Content</a>

The target is often designated as the main element on the page. It should have an id attribute set to the value of the skip link’s href.

<main id="main-content">
...
</main>

Update: Targeting the <main> element is not ideal as it can cause mouse clicks to scroll unexpectedly to the top of the page. A better solution is to add a named anchor tag as the first element within the <main> tag and designate that as the target.

<main>
  <a name="main-content"></a>
  ...
</main>

If there is no element with this id on the page, the skip link does not work. This may seem obvious, but I have seen it occur on multiple sites for various reasons.

  • The website has multiple page templates, one or more of which have an outermost tag surrounding the page’s main content with a missing id attribute. This can occur when legacy or less frequently used templates are overlooked, or when a template contains conditional logic that obscures how and where the top-level tag is rendered.
  • There are multiple widgets that may render the first element of the main content of different pages on a website. Again, this is usually due to code being missed when the skip link is added.

The best ways to ensure skip link targets are present on every page are to minimize and diligently track the locations where they are rendered.

  • Render the top-level content tag in a single location so the skip link target designation only needs to happen once. Many web frameworks and themes use this approach to organize their code and avoid repeated logic.
  • Document each location in the code base that can render a skip link target, both with comments in the source code and in an external list.

The skip link is not in the tab order

By default, anchor tags are interactive and receive focus as a keyboard user tabs through the webpage. However, adding a tabindex attribute with a value of -1 will remove a normally interactive element from the tab order. I have encountered skip links coded like this.

<a href="#main-content" tabindex="-1">Skip to Main Content</a>

Whether this was done by accident or some misguided attempted to prevent the skip link from visually appearing on the page, it makes the skip link inaccessible to keyboard users. The tabindex="-1" attribute removes the skip link from the tab order so it cannot be reached with the Tab key. Although screen readers can still get to the skip link via their special navigation commands, this is inadequate as screen reader users typically also take advantage of standard keyboard commands like Tab and Shift+Tab built into browsers.

The fix? Leave the skip link anchor tag alone and don’t try to cleverly over-engineer it with a tabindex or other HTML, CSS, or JavaScript tricks. Just ensure it’s the first focusable element on the page.

The target element is not focusable

Update: If the skip link targets a named anchor tag for its target, this element is focusable by default, which avoids this problem altogether.

<main>
  <a name="main-content" tabindex="-1"></a>
  ...
</main>

The tabindex="-1" attribute removes the named anchor from the tab order to prevent keyboard users from encountering an empty link, which could cause confusion. It also facilitates correct behavior for users who follow the skip link by pressing Enter or Space, and then use screen reader navigation controls to move to elements adjacent to the target.

If it is absolutely infeasible to use a named anchor target, you can try the solution outlined below, but it suffers from inconsistencies between screen readers.


A skip link’s target element needs to be focusable. However, the <main> tag and most other elements assigned its role (via a role="main" attribute) are normally excluded from the tab order and cannot receive focus by default. It is therefore a good idea to add a tabindex="-1" to the target element. The -1 value makes it possible for the element to receive keyboard focus without putting it into the tab order.

<main id="main-content" tabindex="-1">
...
</main>

When the tabindex="-1" attribute is omitted from the target, it leads to inconsistent behavior. While browsers may still transfer keyboard focus to the target when the skip link is followed, screen reader focus does not move to an unfocusable element. This means that after jumping to the target, pressing Tab or Shift+Tab may move keyboard focus to the next or previous interactive tag, respectively. However, using screen reader commands such as VoiceOver’s Command+Option+Left/Right Arrow to move focus to the previous or next element will not work as expected. Instead, these commands transfer focus to the elements adjacent to the skip link. This can be disorienting to screen reader users, as it’s almost certainly not the anticipated behavior.

Adding a tabindex="-1" attribute to the target element ensures it will receive both keyboard and screen reader focus. When the skip link is followed, subsequent keyboard and screen reader navigation commands will move focus in relation to the target.

Unfortunately, using the screen reader command to move focus from the target to the next element yields inconsistent results across different screen readers. This might be due to bugs or different implementation choices by screen reader vendors. In my testing, I observed the following behavior when using a screen reader command to access the next element after pressing Enter or Space to follow the skip link to its target.

Screen Reader Web Browser Operating System Behavior After Target Focus
JAWS Chrome Windows First element after target skipped
JAWS Edge Windows First element after target skipped
NVDA Chrome Windows Target element announced
NVDA Edge Windows Target element announced
VoiceOver Chrome mac OS Target element focused, element name announced, but content not announced
VoiceOver Safari mac OS Target element focused, element name announced, but content not announced

Despite this variable behavior between screen readers, the effect of the tabindex="-1" attribute seems preferable to what happens when it is not present. Better to move within the main content than unexpectedly revert to the skip link’s location. Further, the tabindex="-1" attribute makes it explicit and clear in the code that the target can receive focus.

The ARIA-AT Project — where “ARIA-AT” stands for “Accessible Rich Internet Applications [for] Assistive Technologies” — is working to “empower equal inclusion by realizing interoperability for AT users.” Hopefully, their efforts to standardize user experience across screen readers will eliminate inconsistencies like this one.

Conclusion

Skip links are essential components for any accessible website with header navigation, and their implementation should be intentional and thoughtful. A skip link needs to be in the tab order, and its target element must be present and focusable on every page.

I want to thank the following individuals for their assistance in correcting and enhancing information in this post.

  • John Jameson for explaining how named anchors are a better choice for skip link targets than elements with the main role.
  • Diane Ko for pointing out an error with how tabindex=”0″ works. (I have corrected this mistake, so the 0 value is no longer present.)
  • Adrian Roselli for guidance on Slack etiquette when sharing and seeking feedback on articles, as well as giving credit to those who provide feedback that results in corrections and improvements to said articles.

Have you encountered other issues with skip links? What techniques do you use to make certain that skip links are accurate and work prope

Share This