I Used The Web For A Day With Just A Keyboard
This article is part of a series in which I attempt to use the web under various constraints, representing a given demographic of user. I hope to raise the profile of difficulties faced by real people, which are avoidable if we design and develop in a way that is sympathetic to their needs. Last time, I used the web for a day without JavaScript. Today, I forced myself to navigate the web using just my keyboard.
Who Uses The Keyboard To Navigate?
Broadly, there are three types of keyboard users:
- Mobility-impaired users who struggle to use a mouse,
- Vision-impaired users who are unable to see clickable elements in the page,
- Power users who are able to use a mouse but find it quicker to use a keyboard.
How Many Users Are We Talking?
I’ve trawled the web for statistics on keyboard usage, and I couldn’t find a thing. Seriously. Not one study.
Most keyboard accessibility guidance sites simply take for granted that “many users” rely on keyboards to get around. Anyone trying to get an approximate number is usually preachily dismissed with “stats don’t matter — your site should be accessible, period.”
Yes, it is true that the scale of non-mouse usage is a moot point. If you can make a change that empowers even one user, it is a change worth making. But there are plenty of stats available around things like color blindness, browser usage, connection speeds and so on — why the caginess around keyboard statistics? If the numbers are as prevalent as sites seem to suggest, surely having them would enable a stronger business case and make defending keyboard accessibility to your stakeholders easier.
The closest thing to a number I can find is an article on PowerMapper, which suggests that 7% of working-age adults in the US, UK, and Canada have “severe dexterity difficulties.” This would make them “unlikely to use a mouse, and rely on the keyboard instead.”
Users with severe visual disabilities use software called a screen reader, which is software that reads out content on the screen as synthesized speech. Like sighted users, non-sighted users want to be able to scan pages for interesting information, so the screen reader has keyboard shortcuts for navigating via headings and links, and relies on keyboard focusable elements for interaction.
“People who are blind need full keyboard access. Period.”
— David Macdonald, co-editor of Using WAI ARIA in HTML5
These same users also have screen readers on their mobile devices, where they use swipe gestures instead of keyboard presses to ‘tab around’ content. So whilst they’re not literally using a keyboard, they do require the site to be keyboard-accessible as the screen reader technology hooks into the same tab ordering and event listeners as if they were using a keyboard. It’s worth noting that only about two-thirds to three-quarters of screen reader users are blind, meaning the rest might use a combination of screen-reader and magnification techniques.
2.3% of American people (of all ages) have a visual disability, not all of which would necessarily warrant the use of a screen reader. In 2016, Addy Osmani estimated actual screen reader usage to be around 1 to 2%. If we factor these users in with our mobility-impaired users and our power users, keyboard usage adds up to a sizeable percentage of the global audience. Therefore, caring about keyboard accessibility is not just doing the right thing morally (and legally — many countries require websites to be accessible by law), but it also makes good business sense.
With all of that in mind, what is the state of the web today? Time to find out!
The Experiment
What does everyone do when they have a day’s worth of intimidating work ahead of them? Procrastinate! I headed over to youtube.com. I had a specific video in mind and was grateful to find I wouldn’t need to tab into the main search box, as it is focussed on page load by default.
The autofocus
Attribute
I assumed this would be focussed with JavaScript on window load, but it’s actually handled by the browser with an autofocus
attribute on the input element.
As a sighted keyboard user, I found this extremely useful. As a blind screen reader user, I’m not sure whether I’d like it or not. The consensus seems to be that judicious use of autofocus
is OK, in cases where the sole purpose of the page is to interact with a form (e.g. Google landing page, or a site contact form).
Default Focus Styles
I searched for some Whose Line Is It Anyway? goodness, and couldn’t help noticing that YouTube hadn’t defined any custom :focus
styles, instead relying on the browser’s native styling to visually indicate which elements I was tabbing through.
I’ve always been under the impression that not all browsers define their own :focus
state, so you have to define your own custom styling. I decided to put this to the test and see which browsers neglect to implement a default style, but to my surprise, I couldn’t find one. Every browser I tested had its own native implementation of :focus
, although each varied in style.
I even went quite far back in time:
If you’d like to see more, there is a comprehensive screenshot collection of different elements in browser native states.
What this tells me is that you can reasonably assume every browser comes with some basic :focus
styling. It is OK to let the browser do the work. What you’re risking is inconsistency: all browsers style elements subtly differently, and some are so subtle that they’re not particularly visually accessible.
It is possible to disable the default browser focus styles — by setting outline: none
on your element — but you should do this only if you implement your own styled alternative. Heydon Pickering recommends this approach, citing the unclear or ugly defaults used by some browsers. If you do decide to roll out your own styles, be sure to use more than just colour as a modifier: Add an outline or an underline or some other visual indicator to support users with color-blindness.
Many sites suppress default focus styles but fail to provide custom styles, leading to inaccessible experiences. If your site is using Eric Meyer’s CSS reset, it could be inaccessible; this commonly used file resets the default :focus
styles but instructs the developer to write their own, and many fail to spot the instructions.
Some people argue that it can be confusing to the user if you disable the browser defaults, as they lose the visual affordance of the focus state they’re used to and instead have to learn what your site’s focus state looks like. On the other hand, some argue that the browser defaults are ugly, or even confusing to the non-keyboard user.
Why confusing? Well, check out this animated carousel format on the BBC. There are two navigation buttons — next, and previous — and it’s useful to the keyboard user that the focus remains on them throughout the narrative. But to the mouse user, it can be quite confusing that the clicked button is still ‘focussed’ after moving the cursor away.
The :focus-visible
CSS Selector
If you want the best of both worlds, you may want to explore the CSS4 :focus-visible
pseudo-class, which will let you provide different focus styling depending on context. :focus-visible
styling only targets elements that have been focussed with keyboard, not with mouse click. This is super cool, though is currently only natively supported in Firefox. It can be enabled in Chrome by turning on the ‘Experimental Web Platform Features’ flag.
YouTube Videos And Keyboard Accessibility
YouTube does a great job with its video player — every part of the player is keyboard navigable. I like how the volume controls slide out when you tab focus away from the mute icon, in contrast to sliding out when hovering over the mute icon.
What I didn’t like was that helpful labels, such as the ‘Mute’ text that appears when hovering over the mute icon, don’t get shown on focus.
Another area that lets YouTube down is that it suppresses some focus styling. Here was me trying to tab to the ‘Show more’ button.
I accidentally tabbed right past the ‘Show more’ button because I couldn’t see any :focus
styling applied, whether custom or native. I figured out that the native styling was being overridden with outline-width
:
GitHub Keyboard Accessibility
OK, work time. Where better to work than at the home of code, github.com?
I noticed three things about GitHub: One great, one reasonable, and one bad.
First, the good.
‘Skip To Content’ Link
GitHub offers a Skip to content
link, which skips over the main menu.
If you hit ENTER while focussed on the ‘Skip to content’ link, you skip all of the menu items at the top of the page and can start to tab within the main area of content, saving time when navigating. This is a common accessibility pattern that is super useful for both keyboard and screen reader users. Around 30% of screen reader users will use a skip link if you provide one.
Alternatively, some sites choose to place the main content first in the reading order, above the navigation. This approach has fallen out of fashion as it breaks the guideline of making your DOM content match the visual order (unless your navigation visually appears at the bottom). And whilst this approach means we don’t need a ‘Skip navigation’ link at all, we’d probably want a ‘Skip to navigation’ link in its place.
Tab To See Content
One feature I noticed working differently to the ‘non-keyboard’ version was the code breakdown indicator.
Using the mouse, you can click the colored bar underneath any repository to view a proportional breakdown of the different programming languages used in the repo. Using the keyboard, you can’t actually navigate to the colored bar, but the languages come into view automatically when you tab past the end of the meta information.
This doesn’t really seem necessary — I would happily tab to the colored bar and hit ENTER on that — but this different behavior doesn’t cause any harm either.
Invisible Links
One problematic thing I came across was that there was an “invisible” link after tabbing past my profile picture at the top right. My tab order would tab to the picture, then to this invisible link, and then to the ‘Watch’ button on the repo (see gif below). I had no idea what the invisible link did, so when I recognized I was on it, I hit ENTER and was promptly logged out!
On closer inspection, it looks like I’ve navigated to a “screenreader only” form (sr-only
is a common screen reader class name) which has the ‘Sign out’ feature.
This sign-out link is in addition to the sign-out link on your profile dropdown menu:
I’m not sure that two separate HTML sign-out links are necessary, as a screen reader user should be able to trigger the drop-down and navigate to the main sign-out link. And if we wanted to keep the separate link, I would recommend applying a :focus
styling to the screen-reader content so that sighted users don’t accidentally trigger logging themselves out!
How To Make A ‘Skip To Content’ Shortcut
So how do we recreate that ‘Skip to content’ shortcut? It’s pretty simple to implement, but can be deceptively tricky to get perfect — so here is what I consider to be the Holy Grail of skip links solutions.
‘Skip link’ is alternatively called ‘Skip navigation’, ‘Skip main navigation’, ‘Skip navigation links’, or ‘Skip to main content’. ‘Skip to main content’ is probably the clearest as it tells you where you are navigating to, rather than what you are skipping over.
The shortcut link should ideally appear straight after the opening <body>
tag. It could appear later in the DOM, even after the footer, provided you have a tabindex="1"
attribute to force it to become the first interactive element in the tab order. However, using tabindex with a number greater than zero is generally bad practice and will often result in a warning when using validation tools such as Lighthouse.
It’s not foolproof to rely on tabindex
, as you may have more than one link with tabindex="1"
. In these cases, it is the first link that would get the tab focus first, not any later links. Read more about using the tabindex attribute here, but remember that you’re always better off physically moving your link to the beginning of the DOM to be safe.
<a class="screen-reader-shortcut" href="#main-content">
Skip to main content
</a>
The ‘Skip to main content’ link has limited use to sighted users, who can already skip the navigation by using their eyes. So, whilst some sites keep the skip link visible at all times, the convention nowadays is to keep the link hidden until you tab into it, at which point it is in focus and gains the styling applied by the :focus
pseudo selector.
.screen-reader-shortcut {
position: absolute;
top: -1000em;
}
.screen-reader-shortcut:focus {
position: fixed;
top: 0;
left: 0;
z-index: 999;
/* ...and now any nice styling you want to apply... */
padding: 1em;
background-color: rgb(114, 105, 105);
color: white;
text-decoration: none;
}
So, what are we actually skipping to? What is #main-content
? It can really be anything:
- Inline content
i.e. the id of yourh1
tag:<h1 id="main-content">
. - Container
e.g. the id of the container around your main content such as<main id="main-content">
. - Sibling anchor
You can link to a named tag just above your main content, e.g.<a name="main-content"></a>
. This approach is usually described in older tutorials — I wouldn’t recommend it these days.
For maximum compatibility across all screen readers, I’d recommend linking to the h1
tag. This is to ensure that the content gets read out as soon as you’ve used the skip link. Linking to containers can lead to funny behavior, e.g. the screen reader starting to read out all the content inside the container.
Your #main-content
should also have a tabindex
of -1
, to ensure that it is programmatically focussable. Some screen readers may not obey the skip link otherwise.
<h1 id="main-content" tabindex="-1">This is the title of the page</h1>
One last consideration: legacy browser support. If you have enough users on IE9 or below, you may need to apply a small JavaScript fix to your skip links to ensure that the focus does actually shift as expected and your users successfully skip your navigation.
Why Are We Reinventing The Wheel?
It seems crazy that as web developers we have to implement this ‘skip navigation’ hack on all of our sites as a rule. You would think we could let the standards do the work.
Since HTML5, we’ve had semantic elements such as <main>
, <nav>
and <header>
. Prior to that, we had ARIA landmarks such as role="main"
, role="navigation"
and role="banner"
respectively. In the current landscape of the web, best practice dictates that you need both, i.e. <main role="main">
, which is a horrid violation of the DRY principle, but there we go.
With all this semantic richness, you’d hope that browsers would start natively supporting navigation via these landmark areas, for example by exposing a keyboard shortcut for users to tab straight into the <main>
section of a web page. No such luck — there is no native support at the moment. Your best bet is to use the Landmark Navigation via Keyboard extension for Chrome, Opera or Firefox.
Screen reader users, however, can start navigating directly to these landmark regions. For example, on VoiceOver on Mac, you can hit CTRL + ALT + U to bring up the Landmarks Menu and go to the ‘main’ landmark, which is a quick and consistent shortcut to get to the main content. Of course, this relies on sites marking up their documents correctly.
Here is a good starting point for your site if you’d like it to be navigable via landmark regions:
<body>
<header role="banner">
<!-- Logo and things can go here -->
<nav role="navigation">
<!-- Site navigation links go here -->
</nav>
</header>
<main role="main">
<!-- Main content lives here - including our h1 -->
</main>
<footer role="contentinfo">
<!-- Copyright statement, etc -->
</footer>
</body>
All this markup is thirsty work. Time for a coffee.
Pact Coffee
I remember seeing a flyer for pactcoffee.com… let’s go and take a look!
Cookie Banner
The ‘Cookie policy’ banner is one of the first things you notice here, and dismissing it is almost an instinctive reflex for the sighted mouse user. Some screen reader users may not care about it (if you’re blind, you wouldn’t know it’s there until you reach it), but as a sighted user, you see it, you want to kill it, and in the case of this site, you need to tab past ALL OF THE OTHER LINKS before you can dismiss it.
I used the ChromeLens accessibility extension to trace the tab order of the page:
This can be fixed by either moving the notice to the top of the document (it can still be anchored to the bottom visually with CSS), or by adding a tabindex="1"
to the OK button. I would suggest applying this fix to any content where the expectation is that the user will want to dismiss it.
More Invisible Links
Like on GitHub, I found myself tabbing to an off-screen element whose purpose wasn’t clear. It turned out to be a ‘See less…’ toggle that sits behind the ‘See more…’ card.
This is because the ‘hidden’ area isn’t really hidden, it’s just rotated 180 degrees, using:
transform: rotateY(180deg);
…which means the ‘See less…’ button is still part of the tab order. This can be fixed by applying a display: none
until the application is ready to trigger the rotation:
Coffee ordered. It’s now time to carry on with my research.
IT World
I was doing some research for this article and came across a similar experiment to my own; Kevin Purdy browsed the web for seven days using only his keyboard. I find it ironic that I was unable to read his article under the same constraints!
The problem was a full-page cookie banner, requiring me to “Update Privacy Settings” or accept the default cookie settings. No matter how many times I tabbed, I could not focus in on the cookie banner and dismiss it.
I dug into the source code to find out what was going on. For a moment, I thought it might be our arch nemesis, the outline
CSS property.
Inspecting the “Update Privacy Setting” link, I can see an outline: 0
as I suspected. So perhaps I am focussing on the buttons, but there is no visual feedback when that happens?
I tried setting the state to :hover
to see if I was missing out on any styling as a keyboard user:
Sure enough, the link turned a nice, obvious orange colour on hover — something I never saw on focus:
Hoorah! Cracked it! I never saw the :focus
state because custom styling was only being applied on :hover
. I must have skipped past the buttons without even noticing, right?
Wrong. Even when I hack the CSS locally, I could not see any focus styling, meaning I wasn’t even getting as far as tabbing into the cookie modal. Then I realised… the link was missing a href
attribute:
That was the real culprit. The outline: 0
wasn’t the problem — the browser was never going to tab to the link because it wasn’t a valid link!
From the HTML 5.2 specification:
The destination of the link(s) is given by the href attribute, which must be present and must contain a valid non-empty URL potentially surrounded by spaces. If the href attribute is absent, then the element does not define a link.
Giving the links a href attribute — even if it’s just #
— would make them valid links and would add them to the tab order of the page.
Funnily enough, later on that day, I was sent an article on PC World to read and I encountered exactly the same problem.
It seems that both sites were using the same Consent Management Platform (CMP). I did a little digging and deduced that it was affecting a number of sites owned by the same company, and have since contacted them directly with a suggested fix.
Kinetico
My kitchen tap is leaking and I’ve been meaning to get it replaced. I saw an ad in the local paper for kinetico.co.uk, so thought I’d take a look.
I couldn’t navigate to the ‘Kitchen Taps’ section, as the link was tucked away behind a ‘Salt & Cartridges’ parent link which only shows its child links on hover. It’s interesting that the site is forward-thinking enough to provide a ‘Skip to Content’ link (seen briefly in the gif above) but was unable to create an accessible menu!
Here is where the menu goes wrong — it only shows the sub menu when the parent menu item is being hovered over:
Fixing it is easier said than done. In most cases, you can just “double up” your selector to apply to focus too:
li:hover .nav_sub_menu,
li:focus .nav_sub_menu {
}
But this doesn’t work in this case because whilst the <li>
element is hoverable, it isn’t focusable. It’s the link inside the <li>
that is focusable. But the submenu isn’t inside the link, it’s next to it, so we need to apply the sibling selector to show the submenu when the link is in focus.
li:hover .nav_sub_menu,
a:focus + .nav_sub_menu {
}
This tweak means we can see our submenu when we tab to the parent menu item on the keyboard. But what happens when you try to tab into the submenu?
When we tab from the parent menu item, the focus shifts to the first link in the child menu as expected. But this moves focus away from the parent menu link, meaning the submenu gets hidden and the child menu items are removed from the tab order again!
This is a problem that can be solved with :focus-within
, which lets you apply styling to a parent element if it or any of its child elements has the focus. So, in this case, we have to triple up:
li:hover .nav_sub_menu, /* hover over parent menu item, show child menu */
a:focus + .nav_sub_menu, /* focus onto parent menu item, show child menu */
.nav_sub_menu:focus-within { /* focus onto child menu item, keep showing child menu */
}
Our menu is now fully keyboard-accessible through pure CSS. I love creative CSS solutions, but a word of warning here: quite a lot “CSS-only” solutions in the wild fall down when it comes to keyboard navigation. Avoiding JavaScript doesn’t necessarily make a site more accessible.
In fact, a JS-driven menu might be a better shout in this case, as browser support for this solution is still quite poor. :focus-within
can currently only be used in Chrome, Firefox, and Safari. Even in Chrome, I found it to be incompatible with the display: none
logic used to show/hide the child menu; I had to hide my menu items by setting opacity: 0
instead.
OK, I’m done for the day. It’s now time to wind down with a bit of social media.
Facebook does an incredible job here, providing a masterclass in keyboard accessibility.
On the very first TAB press, a hidden menu opens up, providing shortcuts to the most popular sections of the current page and links to other popular pages.
When you cycle through the page sections using the arrow keys, those sections are highlighted visually so that you can see where you would be tabbing to.
The most useful feature is that Facebook provides a OPT + / (or ALT + /) shortcut to get back to the menu at any time, making use of the aria-keyshortcuts attribute.
<div class="a11y-help">
Press opt + / to open this menu
</div>
<div aria-label="Navigation Assistant" aria-keyshortcuts="Alt+/" role="menubar">
<a class="screen-reader-shortcut" tabindex="1" href="#main-content">
Skip to main content
</a>
</div>
Unlike the ‘skip to main content’ link, which is built on top of native anchoring technology and “just works”, the aria-keyshortcuts
attribute requires the author to implement all the keyboard behavior, so you’re going to have to write some custom JavaScript if you want to use this.
Here is some JS which hides and shows the menubar
area, which is a useful starting point:
const a11yArea = document.querySelector('*[role="menubar"]');
document.addEventListener('keydown', (e) => {
if (e.altKey && e.code === 'Slash') {
a11yArea.style.display = a11yArea.style.display === 'block' ? 'none' : 'block';
}
});
Summary
This experiment has been a mixed bag of great keyboard experiences and poor ones. I have three main takeaways.
Keep It Stylish
By far the most common keyboard accessibility issue I’ve faced today is a lack of focus styling for tabbable elements. Suppressing native focus styles without defining any custom focus styles makes it extremely difficult, even impossible, to figure out where you are on the page. Removing the outline is such a common faux pas that there’s even a site dedicated to it.
Ensuring that native or custom focus styling is visible is the single most impactful thing you can do in the area of keyboard accessibility, and it’s often one of the easiest; a simple case of doubling up selectors on your existing :hover
styling. If you only do one thing after reading this article, it should be to search for outline: 0
and outline: none
in your CSS.
Semantics Are Key
How many times have you tried opening a link in a new tab, only for your current window to get redirected? It happens to me every now and again, and annoying as it is, I’m lucky that it’s one of the only usability issues I tend to face when I use the web. Such issues arise from misusing the platform.
Let’s look at this code here:
<span onclick="window.location = 'https://google.com'">Click here</span>
An able, sighted user would be able to click on the <span>
and be redirected to Google. However, because this is a <span>
and not a link or a button, it doesn’t automatically have any focusability, so a keyboard or screen reader would have no way of interacting with it.
Keyboard-users are standards-reliant users, whereas the able, sighted demographic is privileged enough to be able to interact with the element despite its non-conformance.
Use the native features of the platform. Write good, clean HTML, and use validators such as https://validator.w3.org to catch things like missing href
attributes on your anchors.
Content Is Key
You may be required to display cookie notices, subscription forms, adverts or adblock notices.
Do what you can to make these experiences unobtrusive. If you can’t make them unobtrusive, at least make them dismissible.
Users are there to see your content, not your banners, so put these dismissible elements first in your DOM so that they can be quickly dismissed, or fall back to using tabindex="1"
if you can’t move them.
Finally, support your users in getting to your content as quickly as they can, by implementing the Holy Grail of ‘skip to main content’ links.
Stay tuned for the next article in the series, where I will be building upon some of these techniques when I use a screen reader for a day.
Further Reading
- A Web Designer’s Accessibility Advocacy Toolkit
- Mobile Accessibility Barriers For Assistive Technology Users
- How Accessibility Standards Can Empower Better Chart Visual Design
- Better Context Menus With Safe Triangles