While most people browsing the web on a computer use a mouse, many rely on their keyboard instead. Theoretically, using a web page with the keyboard should not be a problem — press the TAB key to move the keyboard focus from one focusable element to the next then press ENTER to activate, easy! However, many (if not most) websites tend to have a menu of links at the top of the page, and it can sometimes take a lot of presses to get to the content that you want. Take the homepage of 20min for example, one of the biggest news sites here in Switzerland. You want to read the top story? That’ll be the best part of 40 key presses — not the best best use of anyone’s time.
So, if you want keyboard users to actually use your website rather than getting bored and going somewhere else, you need to do a bit of work behind the scenes to make skipping straight to your main content quicker and easier. You can find all sorts of techniques for this scattered across the web (including here at CSS-Tricks) but most are missing a trick or two and many recommend using outdated or deprecated code. So, in this article, I’m going to take a deep dive into skipping to content and cover everything in a 2021-friendly fashion.
Two types of keyboard users
Although there are numerous differences in the exact type of keyboard or equivalent switch device that people use to navigate, from a coding point of view, we only need to consider two groups:
- People who use the keyboard in conjunction with a screen reader — like NVDA or JAWS on a PC, or VoiceOver on a Mac — that reads the content of the screen out loud. These devices are often used by people with more severe visual impairments.
- All other keyboard users.
Our skip-to-content techniques need to cater to both these groups while not getting in the way of all the mouse users. We will use two complementary techniques to get the best result: landmarks and skip links.
Look at a basic example
I created an example website I’m calling Style Magic to illustrate the techniques we’re covering. We’ll start off with it in a state that works fine for a mouse user but is a bit of a pain for those using a keyboard. You can find the base site and the versions for each of the techniques in this collection over at CodePen and, because testing keyboard navigation is a little tricky on CodePen, you can also find standalone versions here.
Try using the TAB key to navigate this example. (It’s easier on the standalone page; TAB to move from one link to the next, and SHIFT+TAB to go backwards.) You will find that it’s not too bad, but only because there aren’t many menu items.
If you have the time and are on Windows then as I’d also encourage you to download a free copy of the NVDA screen reader and try all the examples with that too, referring to WebAIM’s overview for usage. Most of you on a Mac already have the VoiceOver screen reader available and WebAIM has a great intro to using it as well.
Adding landmarks
One of the things that screen reading software can do is display a list of landmarks that they find on a web page. Landmarks represent significant areas of a page, and the user can pull up that list and then jump straight to one of those landmarks.
If you are using NVDA with a full keyboard, you hit INS+F7 to bring up the “Elements List” then ALT+d to show the landmarks. (You can find a similar list on VoiceOver by using the Web Item Rotor.) If you do that on example site, though, you will only be presented with an unhelpful empty list.
Let’s fix that first.
Adding landmarks is incredibly easy and, if you are using HTML5, you might already have them on your website without realizing it, as they are directly linked to the HTML5 semantic elements (<header>
, <main>
, <footer>
, and so on).
Here’s a before and after of the HTML used to generate the header section of the site:
<div class="bg-dark">
<div class="content-width flex-container">
<div class="branding"><a href="#">Style Magic</a></div>
<div class="menu-right with-branding">
<a href="#">Home</a>
<!-- etc. -->
</div>
</div>
</div>
Becomes
<div class="bg-dark">
<header class="content-width flex-container">
<section class="branding"><a href="#">Style Magic</a></section>
<nav aria-label="Main menu" class="menu-right with-branding">
<a href="#">Home</a>
<!-- etc. -->
</nav>
</header>
</div>
The classes used remain the same, so we don’t need to make any changes at all to the CSS.
Here’s a full list of the changes we need to make in our example site:
- The
<div>
denoting the header at the top of the page is now a<header>
element. - The
<div>
containing the branding is now a<section>
element. - The two
<div>
s containing menus have been replaced with<nav>
elements. - The two new
<nav>
elements have been given anaria-label
attribute which describes them: “Main menu” for the menu at the top of the page, and “Utility menu” for the menu at the bottom of the page. - The
<div>
containing the main content of the page is now a<main>
element. - The
<div>
denoting the footer at the bottom of the page is now a<footer>
element.
You can see the full updated HTML on CodePen.
Let’s try that landmark list trick in NVDA again (INS+F7 then ALT+d — here’s the link to the standalone page so you can test yourself):
Great! We now have the banner
landmark (mapped to the <header>
element), Main menu; navigation
(mapped to the top <nav>
element, and displaying our aria-label
), main
(mapped to <main>
) and content info
(mapped to footer
). From this dialog I can use TAB
and the cursor keys to select the main
landmark and skip straight to the content of the page, or even better, I can just press the D
key when browsing the page to jump from one landmark role directly to the next. Users of the JAWS screen reader have it even easier — they can simply press Q
when browsing to jump straight to the main
landmark.
As an added bonus, using semantic elements also help search engines understand and index your content better. That’s a nice little side benefit of making a site much more accessible.
Adding a skip link
I expect you’re sitting back thinking “job done” at this point. Well, I’m afraid there’s always a “but” to consider. Google did some research way back in 2011 on the use of CTRL+f to search within a web page and found that a startling 90% of people either didn’t know it existed, or have never used it. Users with a screen reader behave in much the same way when it comes to landmarks — a large portion of them simply do not use this feature even though it’s very useful. So, we’re going to add a skip link to our site to help out both groups as well as all those keyboard users who don’t use a screen reader.
The basic requirements for what makes a good skip link are:
- It should be perceivable to all keyboard users (including screen reader users) when it is needed.
- It should provide enough information to the keyboard user to explain what it does.
- It should work on as wide a range of current browsers as possible.
- It should not interfere with the browsing of a mouse user.
Step 1: Improving the keyboard focus appearance
First up, we’re going to improve the visibility of the keyboard focus across the site. You can think of the keyboard focus as the equivalent to the position of the cursor when you are editing text in a word processor. When you use the TAB key to navigate the keyboard focus moves from link to link (or form control).
The latest web browsers do a reasonable job of showing the position of the keyboard focus but can still benefit from a helping hand. There are lots of creative ways to style the focus ring, though our goal is making it stand out more than anything.
We can use the :focus
pseudo-class for our styling (and it’s a good idea to apply the same styles to :hover
as well, which we’ve already done on the example site — CodePen, live site). That’s the very least we can do, though it’s common to go further and invert the link colors on :focus
throughout the page.
Here’s some CSS for our :focus
state (a copy of what we already have for :hover
):
a:focus { /* generic rule for entire page */
border-bottom-color: #1295e6;
}
.menu-right a:focus,
.branding a:focus {
/* inverted colors for links in the header and footer */
background-color: white;
color: #1295e6;
}
Step 2: Adding the HTML and CSS
The last change is to add the skip link itself to the HTML and CSS. It consists of two parts, the trigger (the link) and the target (the landmark). Here’s the HTML that I recommend for the trigger, placed right at the start of the page just inside the <header>
element:
<header class="content-width flex-container">
<a href="#skip-link-target" class="text-assistive display-at-top-on-focus">Skip to main content.</a>
<!-- etc. -->
</header>
And here’s the HTML for the target, placed directly before the start of the <main>
content:
<a href="#skip-link-target" class="text-assistive display-at-top-on-focus" id="skip-link-target">Start of main content.</a>
<main class="content-width">
<!-- etc. -->
</main>
Here’s how the HTML works:
- The skip link trigger links to the skip link target using a standard page fragment (
href="#skip-link-target"
) which references theid
attribute of the target (id="skip-link-target"
). Following the link moves the keyboard focus from the trigger to the target. - We link to an anchor (
<a>
) element rather than adding theid
attribute directly to the<main>
element for two reasons. First, it avoids any issues with the keyboard focus not moving correctly (which can be a problem in some browsers); secondly, it means we can provide clear feedback to the user to show that the skip link worked. - The text of the two links is descriptive so as to clearly explain to the user what is happening.
We now have a functioning skip link, but there’s one problem: it’s visible to everyone. We’ll use CSS to hide it from view by default, which keeps it out of the way of mouse users, then have it appear only when it receives the keyboard focus. There are lots of ways to do this and most of them are okay, but there’s a couple of wrong ways that you should avoid:
- Do: use
clip-path
to make the link invisible, or usetransform: translate
orposition: absolute
to position it off screen instead. - Don’t: use
display: none
,visibility: hidden
, thehidden
attribute, or set thewidth
orheight
of the skip link to zero. All of these will make your skip link unusable for one or both classes of keyboard users. - Don’t: use
clip
as it is deprecated.
Here’s the code that I recommend to hide both links. Using clip-path
along with its prefixed -webkit-
version hits the spot for 96.84% of users at time of writing, which (in my opinion) is fine for most websites and use cases. Should your use case require it, there are a number of other techniques available that are detailed on WebAIM.
.text-assistive {
-webkit-clip-path: polygon(0 0, 0 0, 0 0, 0 0);
clip-path: polygon(0 0, 0 0, 0 0, 0 0);
box-sizing: border-box;
position: absolute;
margin: 0;
padding: 0;
}
And to show the links when they have focus, I recommend using a version of this CSS, with colors and sizing to match your branding:
.text-assistive.display-at-top-on-focus {
top: 0;
left: 0;
width: 100%;
}
.text-assistive.display-at-top-on-focus:focus {
-webkit-clip-path: none;
clip-path: none;
z-index: 999;
height: 80px;
line-height: 80px;
background: white;
font-size: 1.2rem;
text-decoration: none;
color: #1295e6;
text-align: center;
}
#skip-link-target:focus {
background: #084367;
color: white;
}
This provides for a very visible display of the trigger and the target right at the top of the page where a user would expect to see the keyboard focus directly after loading the page. There is also a color change when you follow the link to provide clear feedback that something has happened. You can fiddle with the code yourself on CodePen (shown below) and test it with NVDA on the standalone page.
Taking this further
Skip links aren’t just for Christmas your main menu, they are useful whenever your web page has a long list of links. In fact, this CodePen demonstrates a good approach to skip links within the content of your pages (standalone page here) using transform: translateY()
in CSS to hide and show the triggers and targets. And if you are in the “lucky” position of needing to support older browsers, then you here’s a technique for that on this CodePen (standalone page here).
Let’s check it out!
To finish off, here are a couple of short videos demonstrating how the skip links work for our two classes of keyboard user.
Here’s how the finished skip link works when using the NVDA screen reader:
Here it is again when browsing using the keyboard without a screen reader:
We just looked at what I consider to be a modern approach for accessible skip links in 2021. We took some of the ideas from past examples while updating them to account for better CSS practices, an improved UI for keyboard users, and an improved experience for those using a screen reader, thanks to an updated approach to the HTML.
Not sure if this is still relevant, but there is an issue on the HTML5 Boilerplate repo that calls out clip-path: “clip-path in visuallyhidden destroys scrolling performance.”
https://github.com/h5bp/html5-boilerplate/issues/2021
Anyone know if this is still relevant?
It took a bit of digging to find the relevant bug report (here: https://bugs.chromium.org/p/chromium/issues/detail?id=611257) — it looks like this was fixed in Chrome 85 released in August 2020
Great article, thanks. If you have multiple skip links grouped together (for example skip to main navigation, skip main content, skip to search, skip to site index), should they be grouped in a
<nav aria-label="Skip links">
element?Thanks, glad you liked it! If you have a bunch of skip links grouped together then I would mark them up the same as any other navigation menus so
<nav aria-label="Skip links">
would be appropriate in my opinion. It’s pretty unusual to have lots of skip links like this though so I would only want to do this if it was a pretty complex page or a web app for example.Does not work at all with Vivaldi. None of the page is accessible with tab from Vivaldi browser unlike any other website. Works perfectly in Firefox
By default Vivaldi uses a different method for keyboard navigation than other browsers called ‘Spatial Navigation’ described here: https://help.vivaldi.com/desktop/shortcuts/spatial-navigation/ — basically you use
Shift
plus the cursor keys to move visually around the links etc. on the page, an approach that makes the skip link unnecessary for keyboard users who can see well. The same system was used by Opera before it switched to using Blink. You can flip Vivaldi to the conventionalTAB
method of keyboard navigation by turning off Spatial Navigation and setting ‘Webpage Focus’ to ‘Focus All Controls and Links’.Thanks! Great read, and quite informative.
Thanks for this post. I’ve been using skip-to-content links already but it’s nice to have an update for 2021! Good job
Thanks for this writeup. I’d suggest a couple of minor tweaks if you’re going to skip to an actual link rather than programmatically focusing the region (which is what I tend to do). First, I’d recommend putting it inside the ; this way screen readers will announce the main landmark when the link takes focus. Doing this, “start of main content” wording becomes unnecessary (and it’s not really true if the link is outside the anyway). Removing the link text, however, results in NVDA announcing the link as “main landmark, clickable”. Putting aria-hidden=”true” on the link still focuses it but announces nothing for the link itself, and you still get the main landmark announcement.
Thanks for the feedback! The issue for me with removing the link text on the target link is that we would then lose important feedback that is in place for those keyboard-only users that are not using screen readers. I’m in two minds about placing the target link within the
<main>
element. It could make a little more sense semantically but if a screen reader user uses the landmark role to skip to content then they get the link read out first rather than the content itself, plus I’m not sure if it might have a (minor) negative SEO impact as well hence I plumped for placing it directly before the<main>
rather than within it.Hey Paul, thanks for your reply in return (although I don’t have a reply link for your comment, so it might end up looking like I’m replying to myself). I’m curious about the remark about losing important feedback for sighted keyboard users; the page normally scrolls to where focus is taken, so that should suffice for visual feedback. You’re correct about the link being read out before the content (although the main landmark should be announced before the link text), which is why I’m inclined to aria-hide it. I’m not sure about SEO impact, I’d be inclined to think it would be minimal if any (testing surely required to know), but given my job focus I’m typically more worried about accessibility :) In the end of course there are lots of quibbles when it comes to accessibility implementation but having a skip link is miles better than not having one even if implementation varies slightly.
Pardon all the comments, but I ended up writing a blog post with my thoughts on this: https://plousia.com/blog/some-thoughts-css-tricks-deep-dive-skipping-content (hope links are allowed). Please keep in mind these are aimed at making an accessible implementation even better, not being overly critical – the main thing is having a functioning skip link which puts you miles ahead of sites that don’t.
Feedback always welcome! Just wanted to clarify my point about the feedback for keyboard-only users that aren’t using a screen reader. You said ‘the page normally scrolls to where focus is taken’ when a page fragment is used for navigation but this isn’t really the case: first, the page needs to be long enough for a scroll to be possible otherwise nothing happens at all. The scroll position can also end up quite screwy when there are
fixed
orsticky
position elements. So, this method avoids these problems and ensures there’s always positive visual feedback on where the keyboard focus went. Regarding the SEO point, search engines use the<main>
element to help them identify the most important aspect of a page, with the content at the very top of this section being given most weight. So, keeping the link out of<main>
helps make the content of the page that little bit clearer to them.Thanks, great article! It’s timely for me, as more and more of my clients are asking for accessibility features.