Keyboard accessibility is an important part of the user experience. There are multiple criteria in Web Content Accessibility Guidelines (WCAG) about this topic. Still, it’s somehow overlooked, affecting the experience of many users, mainly people with motor disabilities — any condition that limits movement or coordination.
Certain conditions like having a broken arm, the loss or damage of a limb, muscular dystrophy, arthritis, and some others can make it impossible for a person to use a mouse to navigate a site. So, making a site navigable via keyboard is a very important part of ensuring the accessibility and usability of our websites.
In this article, I’ll cover how to use HTML and CSS to create an accessible experience for keyboard users while mentioning what WCAG criteria we should keep into consideration.
One of the basics of creating a website accessible site for keyboard users is knowing what elements should be navigable via keyboard. For this, a good HTML semantic is crucial because it’ll indicate the kind of elements we want to focus on with keyboard navigation.
When a user presses the Tab key, it’ll let them select the next focusable element in the HTML, and when they press the keys Shift + Tab, it’ll take them to the last focusable element. With that said, what elements need to be focusable? Anything that requires user interaction. Between them, you can find the elements
select, and the controls of elements
video (when you add the attribute
controls to them). Additionally, certain attributes can make an element keyboard navigable, such as
tabindex. In the case of Firefox, any area with a scroll will also be keyboard focusable.
Additionally to that, you can:
There are probably more interactions, some of which depend on differences between operating systems and browsers, but that covers mostly what you can do with the keyboard.
Does that mean those elements are automatically keyboard-accessible by default? A good HTML structure is very helpful, and it makes content mostly accessible by default, but you still need to cover some issues.
For example, certain input types like
month have popups that can work differently between browsers. Chrome, for example, allows you to open the date picker popup by pressing the Enter or Space key in a designated button in the input. However, with Firefox, you need to press Enter (or Space) in either the day, month, or year fields to open the popup and modify each field from there.
Additionally to the previous point, certain components require elements to be keyboard focusable without being natively selectable. In other cases, we need to manage keyboard focus manually, and our markup needs to help us with that. For both cases, we’ll need to use an HTML attribute that will help us with this task.
This attribute will greatly help us bring keyboard accessibility to more complex component patterns. This attribute receives an integer, and properly using it will help us make a DOM element keyboard focusable. With
tabindex, we can find three different cases:
It causes the element to be keyboard focusable. You usually don’t want to add keyboard focus to an element unless it is not interactive, but some scenarios will require it.
Some people who start with web accessibility think it is a good idea to add the attribute
tabindex="0" to every element because they think it’ll help screen reader users navigate easily through a site. This is a terrible practice because of two reasons:
So, to summarize: it’s a useful technique for some components, but most of the time, you’ll be alright if you don’t use it, and certainly, you must not use it in every single element of your site.
With that in mind, here is where negative
One example of that is tabs. A recommended pattern for this component is ensuring that when you press the Tab key when you’re located in the active
tab, it goes to the active
tabpanel instead of bringing the focus to the next tab. We can achieve that by adding a negative
tabindex to all non-active tabs like this:
We’ll see more examples later about how a negative
tabindex will help us to have more control over focus state management in different components, but keep in mind a negative
tabindex will be important in those cases.
Finally, you can put any negative integer in the
tabindex property, and it’ll work the same.
tabindex="-1000" will make no difference, but my mere convention is that we tend to use
-1 when we use this attribute.
It might be useful if you want keyboard users to focus on widgets before they reach the page content, but that’d be a bit confusing for assistive technology users (like screen readers). So again, you’d be better by creating a logical order in the DOM.
I have to quickly note an incoming attribute that will help us a lot with keyboard accessibility called
inert. This attribute will make the content inaccessible by assistive technologies.
CSS is an essential tool for keyboard accessibility because it allows us to create a level of customization of the experience, which is important for compliance with WCAG 2.2 criteria. Additionally, CSS has multiple selectors with different uses that will help to create a good keyboard experience, but be careful because a bad use of certain properties can be counterproductive. Let’s start diving into the use of this language to create an accessible experience for keyboard users.
When you use a mouse, you can see which element you can interact with it thanks to the cursor, and you wouldn’t remove the cursor from your user, right? That’d make them unable to know what element they want to use!
We have a similar concept for keyboard navigation, and it’s called a focus indicator, which by default is an outline that surrounds a keyboard-focusable element when it’s selected by it. Being sure all your keyboard-focusable elements have a focus indicator is essential to making a website keyboard accessible, according to WCAG criteria:
As you can notice, Chromium-based browsers like Chrome or Edge have a black and white outline, which works in light and dark mode. Firefox opted for a blue outline which is noticeable in both modes. And Safari (and Webkit-based browsers because right now, all iOS browsers use Webkit as their browser engine) looks almost the same as Firefox but with an even subtler outline for a dark color scheme.
This example uses an outline, but remember that you can use other properties to determine the focus state, like
background-color or some creative ways of using
box-shadow as long as it’s compliant with the requirements. However, don’t use the property
outline: none to eliminate the element’s outline.
And then, we use those custom properties to add a global focus rule:
The use of
0.08em here is to give it a bigger outline size if the font is bigger, helping to scale the element’s contrasting area better with the element’s font size.
Keep in mind that even when WCAG mentions that the focusing area “is at least as large as the area of a
1 CSS pixel thick perimeter of the unfocused component”, it also mentions that it needs to have “a contrast ratio of at least
3:1 against adjacent non-focus-indicator colors, or is no thinner than
2 CSS pixels.” So, a minimum thickness of
2px is necessary to comply with WCAG.
Remember that you might need a thicker line if you use a negative
outline-offset because it’ll reduce the perimeter of the outline. Also, using a dashed or dotted outline will reduce the focused area roughly by half, so you’ll need a thicker line to compensate for it.
With CSS, you normally use the pseudo-class
:focus to give style to an element when it’s being focused by a keyboard, and it does its job well. But modern CSS has given us two new pseudo-classes, one that helps us with a certain use case and the other that solves an issue that happens when we use the
focus pseudo-class. Those pseudo-classes are
:focus-visible. Let’s dive into what they do and how they can help us with keyboard accessibility:
This pseudo-class will add a style whenever the element is being focused or any of the element’s children is also being focused. Let’s make a quick example to show how it looks:
If you use keyboard navigation, you’ll notice the order is pretty straightforward. It reads from left to right and from top to bottom, and the navigation will be the same. Now let’s use grid properties to make some changes:
Now it looks completely disarrayed. Sure, the layout looks funny, but when you start navigating it with the Tab key, it’ll have a very random order. There is some degree of predictability now because I used numbers as the button’s label, but what happens if they have different content? It’d be impossible to predict which would be the next button to be focused on with a keyboard.
This is the kind of scenario that needs to be avoided. It doesn’t mean you can’t explicitly order an element within a grid or use the
order property. That means you need to be careful with managing your layouts and be sure the visual and DOM order matches as much as possible.
By the way, if you want to try it by yourself, you can see the demo of this code here and experience this chaotic keyboard navigation by yourself:
Now let’s start styling this component! By default, this element uses this triangle to indicate if the
details element is opened or closed. We can remove that by adding this rule to the
But we’ll still need a visual indicator to show if it’s opened or closed. My solution is to add a second element as a child of
summary. The important part is that this element will have the attribute
The reason why I hid this
span element is that we’ll be modifying its content with CSS modifying the pseudo-element
::before, and the content we add will be read by a screen reader unless, of course, we hide it from them.
With that said, we can change it because the browser manages the open state of the
details element by adding the attribute
open to the container. So we can add and change the content using those CSS rules:
Now, you can add the styling you need to adapt it (remember to use adequate focus states!). You can check this demo I made to see how it works. Test it with a keyboard, and you’ll notice you can interact with it without a problem.
But there can be multiple skip links in a site that will lead you to various parts of the site, as Smashing Magazine does. When you use the Tab Key to navigate this website, you’ll notice there are three skip links, all of them taking you to important points of the page:
They’re usually located on the site’s header, but it’s not always the case. You can add them where needed, as Manuel Matuzović shows in this tweet. He added an inline skip link to a project because the interactive map has a lot of keyboard-focusable elements.
Now, as the usefulness of skip links is clear, let’s create one. It’s very simple; we just need to create an
a element that takes you to the desired element:
Next, we need to hide visually the
a element. What I do there is use the
transform CSS property to remove it from the visual range:
Then, we move it to the needed position when the element is being focused:
And that’s it! Creating a skip link is easy and offers a lot of help for keyboard accessibility.
The container with the class
tooltip-container is there just to allow us to manipulate the container’s position with the attribute
role="tooltip" later using CSS. Speaking of this element, you would think this role adds enough semantics to make it work, but as a matter of fact, it doesn’t, so we’ll have to rely upon a couple of
aria attributes to link it to our
This attribute depends of what’s the intention of the tooltip. If you are planning to use it to name an element, you need to use the attribute
However, if you want to use the tooltip to describe what an element does, you’ll need to link it using the attribute