When is :focus-visible visible?

Focus outlines (and their styling) have frequently been a point of contention between aesthetics and usability. On the one hand, focus outlines are incredibly important for navigation, particularly by users navigating via keyboard or other non-pointer devices. On the other hand, for certain mouse or touchscreen users they can be unnecessary and detract from the design.

Before the :focus-visible CSS pseudo-class was introduced, there was no middle ground to this debate - you either sided with design or accessibility (hopefully the latter). But now, with :focus-visible, we can have focus styles that apply only when the browser / user agent determines that focus should be indicated on the element.

So, when do user agents determine that focus should be indicated? According to the :focus-visible specification, user agents are free to choose their own heuristics for indicating focus. However, the spec includes some suggestions, which most browsers have adopted. These suggestions can be broken down into four questions -

  1. Has the user expressed a preference for always visible focus?
  2. Does the focused element support keyboard input?
  3. Is the user interacting with the focused element with a non-pointing device?
  4. Did a script cause focus to move from a previously visibly-focused element?

Flowchart showing when :focus-visible applies

Let’s break each of these questions down.

1. Has the user expressed a preference for always visible focus? #

This is a simple one. If a user has expressed a preference for focus to always be visible, then the :focus-visible pseudo-class should apply to the focused element. Fin.

2. Does the focused element support keyboard input? #

If the focused element supports keyboard input, the :focus-visible pseudo-class should apply. Elements that support keyboard input are typically form elements, such as <input> or <textarea>, basically any element that would trigger a virtual keyboard to be shown in the absence of a physical one.

In the example below, we have both a <button> and an <input>. For the <button>, whether the :focus or :focus-visible style is applied depends on how you interact with the element (more on that in the next section). But regardless of how you interact with the <input> element, it always displays both the :focus style and the :focus-visible style of the red border.

See the Pen Untitled by Ire Aderinokun (@ire) on CodePen.

3. Is the user interacting with the focused element with a non-pointing device? #

Next, if the user is interacting with the focused element using a keyboard or another non-pointing device, the :focus-visible pseudo-class should apply. So regardless of the type of element it is, focus should be visible.

In the example below, the <button> elements display the :focus and/or :focus-visible styles depending on what device you use to interact with it.

See the Pen :focus-visible - Is the user interacting with a non-pointing device? by Ire Aderinokun (@ire) on CodePen.

4. Did a script cause focus to move from a previously visibly-focused element? #

Finally, this question is to do with what happens if focus is moved via a script instead of user input. According to this guideline, the :focus-visible pseudo-class should apply based on the state of the previously focused element. If :focus-visible applied to the previously focused element, then it should also apply to the currently focused element.

We can see this play out in the example below. Depending on how you interact with the “reset” button, the :focus and/or :focus-visible styles will apply to the test button when focus is programatically moved.

See the Pen :focus-visible - script by Ire Aderinokun (@ire) on CodePen.

Bonus - User agent wildcard #

As a reminder, these are mostly guidelines/suggestions and user agents are still able to make decisions about when they want :focus-visible to apply. From my tests, I haven’t seen any of the major browsers respond in a different way, but it’s something to remember.

Because of this, best practice is always to only remove :focus styles if :focus-visible is not also applied to the element. Here’s what that looks like:

/* Apply focus styles */
:focus-visible, :focus {
outline: 1px solid red;
}

/* Remove the focus outline */
:focus:not(:focus-visible) {
outline: none;
}

Keep in touch KeepinTouch

Subscribe to my Newsletter 📥

Receive quality articles and other exclusive content from myself. You’ll never receive any spam and can always unsubscribe easily.

Elsewhere 🌐