Focus Style CSS for Keyboard Navigation

Last Updated:
White keyboard

Making fully accessible websites should be a priority for any front end developer. Most HTML has accessibility baked in so our lives should be slightly easier. That said, as we create custom components and customise styling, ensuring all users are given the best experience can be a bit trickier.

Table of Contents

Keyboard only focus state

An issue that I came across recently was that of needing keyboard only focus state on a button. As the button triggered an action on the page and did not navigate the user away from that page the focus state stayed highlighted. This is not particularly beneficial for any user, as the button has already been interacted with. In short, :focus is applied not only when you have ‘focused’ on the element, but also just after you have clicked on it.

My button needed to have a focus state added when a user was navigating by keyboard, but not by any other means. If they use the tab button, focus is applied. But not when using a mouse.

What about :focus-visible?

I came across :focus-visible, and it looked like the perfect solution. Mozilla says:

This selector is useful to provide a different focus indicator based on the user’s input modality (mouse vs. keyboard).

The difference between :focus and :focus-visible is that :focus-visible comes into play when a user navigates with a keyboard. A mouse does not trigger it. Sounds great!

Unfortunately it is only implemented within Chrome, and even then it is behind a flag. Very limited uptake.

Update

focus-visible is now available to use across all browsers (apart from I.E., but… you know).

I will leave the rest of the article untouched as it describes one way of handling focus state. However moving forward I would recommend using focus-state. It is more light weight than the below solution.

Using HTML and CSS

Turns out there is a very elegant solution to this, as Roman Komarov talks about in his article. Roman shows that it is possible to achieve keyboard only navigation with focus state for buttons and links using an inner span or div. The below shows how to handle keyboard only focus styling:

Use the tabindex

By setting the inner span or divs tabindex to -1, it can be made to be artificially focusable. MDN covers this in their piece on the tabindex saying:

A negative value (usually tabindex=”-1″) means that the element is not reachable via sequential keyboard navigation.

What this means is that the element can be focused on by a mouse, but not a keyboard. So when the outer element (button, or link) gets clicked the inner element gets the focus. The HTML looks like this:

<button class="button" type="button">
    <span class="button_inner" tabindex="-1">
        Click away!
    </span>
</button>

Removing the :focus

Now that conditional focus has been set up using the HTML elements, appropriate styling needs to be added.

Firstly let’s remove :focus off both elements. This is not needed as we are looking for CSS focus only on keyboard.

.button:focus,
.button_inner:focus {
    outline: none;
}

Then focus styling is added to the inner span or div when the outer element has focus, and only then.

.button:focus > .button_inner  {
    border: 2px solid red; /* A terrible style to use, but easy to see for this example! */
} 

Solution to CSS focus only on keyboard

Final code to solve our keyboard navigation with focus state problem: See the CodePen for the keyboard focusable CSS (keyboard only).

To test, click the button and you will see that no focus is added. Then navigate with your browser tab key. This will highlight the button with a red border.

Hopefully :focus-visible will be rolled out to more browsers as that would be a far cleaner solution. However, for now this alternative is fairly easy to implement and achieves the desired result! Now users can get a focus state only when they use the keyboard.

Web accessibility is a topic I feel quite strongly about. If you are interested, you can read more about the ‘Inclusive web’ and why it should matter more to businesses.

[support-block]

If you missed the link above and were scan reading – here is the working code in a Codepen: https://codepen.io/RobertMarshall/pen/YzqdPgm

Related Posts

Helpful Bits Straight Into Your Inbox

Subscribe to the newsletter for insights and helpful pieces on React, Gatsby, Next JS, Headless WordPress, and Jest testing.