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 button, a, input, summary, textarea, select, and the controls of elements audio, and video (when you add the attribute controls to them). Additionally, certain attributes can make an element keyboard navigable, such as contenteditable or 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 date, datetime-local, week, time, and 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.

Before we start this section, we need to keep in mind two concepts: a DOM element is at the same time focusable (that means, you can programmatically focus on it with JavaScript) and tabbable (that means, being able to be selected with the Tab Key).

With that in mind, here is where negative tabindex comes into play because it’ll make an element unable to be tabbed (but you can still focus on it with JavaScript). This is important for specific components because, in some cases, we’ll need to make a normally tabbable element unable to be tabbed, or we’ll need an element to be focusable but not tabbable.

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="-1" and 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.

Now you might be asking yourself how this can be useful because if something removes keyboard accessibility, but in some cases, that’s a good thing! One component that will benefit from it is modals. Adding this attribute to all elements in the site except this modal will make it easy to create a focus trap. So you’ll ensure the user can’t accidentally navigate to other parts of the site using the Tab key unless they close that modal. Right now, creating a keyboard trap requires quite some thinking with JavaScript (I’ll explain how in the second part of this guide). So, having a way to make it easier with this attribute will be handy.

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-within and :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 summary element.

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 aria-hidden="true":

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.

Those little text bubbles that show extra information to an element can be done with pure CSS as well, but a little disclaimer here: it is suggested that you can close a tooltip by pressing the Escape key, which it’s only possible with JavaScript. I’ll explain how to add this feature in the second part of this article, but everything else can be done in a very simple way using HTML and CSS only.

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 button.

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 aria-labelledby:

However, if you want to use the tooltip to describe what an element does, you’ll need to link it using the attribute aria-describedby:

And this is how you create a keyboard-accessible tooltip using HTML and CSS. You can check how both examples of tooltip behave in this demo. Remember, this is not fully ready for production. You need JavaScript to close the tooltip when you press the Esc key. We’ll cover that later in the next part of this article, so keep it in mind.

Keyboard accessibility is an essential part of accessibility. I hope this article has helped you understand how vital HTML and CSS are to make keyboard navigation a good and accessible user experience. That’s not the end of keyboard accessibility, though! I’ll be covering how we can use JavaScript to manipulate keyboard navigation and how we can use it in more complex component patterns.