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 -
- Has the user expressed a preference for always visible focus?
- Does the focused element support keyboard input?
- Is the user interacting with the focused element with a non-pointing device?
- Did a script cause focus to move from a previously visibly-focused element?
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;
}