Enable :Focus Only on Keyboard Use (Or Tab Press)

Enable :focus only on keyboard use (or tab press)

Update: This issue may no longer be relevant

Some other posters have mentioned the :focus-visible pseudo class - which now has decent browser support...

I would like to add, that based on the spec which covers the :focus-visible pseudo class, browsers should now only indicate focus when it is helpful to the user - such as in cases where the user interacts with the page via a keyboard or some other non-pointing device

This basically means that the original issue is no longer relevant, because now, when a user clicks/taps a button (or another focusable element), the User Agent won't show the focus ring anymore - even though the button is focused - because in this case the focus ring isn't helpful to the user.

From the spec:

While the :focus pseudo-class always matches the currently-focused
element, UAs only sometimes visibly indicate focus (such as by
drawing a “focus ring”), instead using a variety of heuristics to
visibly indicate the focus only when it would be most helpful to the
user. The :focus-visible pseudo-class matches a focused element in
these situations only...

Indeed, as of version 90, Chromium’s User Agent stylesheet switched from :focus to :focus-visible, and as a result of this change, button clicks and taps no longer invoke focus rings

Also, as of version 87, Firefox also uses :focus-visible on their User Agent style.

All that being said, if custom focus styles are needed, since focus styles have now shifted from :focus to :focus-visible, when overriding the default styles with custom focus styles - the :focus-visible pseudo class should be used.

Something like this:

button:focus-visible {
/* remove default focus style */
outline: none;
/* custom focus styles */
box-shadow: 0 0 2px 2px #51a7e8;
color: lime;
}

Backwards Compatibility:

The possible problem with using :focus-visible like this, is that browsers which don't support :focus-visible, will show the default focus ring, which may not be clear or visible - depending on the design.

Šime Vidas, in this article, describes a viable strategy to currently use the :focus-visible pseudo class - which would work even in browsers which don't yet support :focus-visible -

A good way to start using :focus-visible today is to define the focus
styles in a :focus rule and then immediately undo these same styles in
a :focus:not(:focus-visible) rule. This is admittedly not the most
elegant and intuitive pattern, but it works well in all browsers:

Browsers that don’t support :focus-visible use the focus styles
defined in the :focus rule and ignore the second style rule completely
(because :focus-visible is unknown to them).

In browsers that do support :focus-visible, the second style rule
reverts the focus styles defined in the :focus rule if the
:focus-visible state isn’t active as well. In other words, the focus
styles defined in the :focus rule are only in effect when
:focus-visible is also active.

button:focus {
outline: none;
background: #ffdd00; /* gold */
}

button:focus:not(:focus-visible) {
background: white; /* undo gold */
}

Original Answer:

This excellent article by Roman Komarov poses a viable solution for achieving keyboard-only focus styles for buttons, links and other container elements such as spans or divs (which are artificially made focusable with the tabindex attribute)

The Solution:

button {
-moz-appearance: none;
-webkit-appearance: none;
background: none;
border: none;
outline: none;
font-size: inherit;
}

.btn {
all: initial;
margin: 1em;
display: inline-block;
}

.btn__content {
background: orange;
padding: 1em;
cursor: pointer;
display: inline-block;
}


/* Fixing the Safari bug for `<button>`s overflow */
.btn__content {
position: relative;
}

/* All the states on the inner element */
.btn:hover > .btn__content {
background: salmon;
}

.btn:active > .btn__content {
background: darkorange;
}

.btn:focus > .btn__content {
box-shadow: 0 0 2px 2px #51a7e8;
color: lime;
}

/* Removing default outline only after we've added our custom one */
.btn:focus,
.btn__content:focus {
outline: none;
}
<h2>Keyboard-only focus styles</h2>

<button id="btn" class="btn" type="button">
<span class="btn__content" tabindex="-1">
I'm a button!
</span>
</button>

<a class="btn" href="#x">
<span class="btn__content" tabindex="-1">
I'm a link!
</span>
</a>

<span class="btn" tabindex="0">
<span class="btn__content" tabindex="-1">
I'm a span!
</span>
</span>

<p>Try clicking any of the the 3 focusable elements above - no focus styles will show</p>
<p>Now try tabbing - behold - focus styles</p>

What is the optimal way of achieving keyboard only :focus styling in 2021?

Now the question would be, is there a better or easier way to achieve this functionality? Is there an industry standard for this that I'm missing?

Short answer: no, you have essentially listed your options here if you are aiming for "perfection" (where it works in all browsers exactly the same).

However all 4 of the options have drawbacks as you stated.

Personally I would go for a "best fit" solution, where some users may end up with focus indicators on click but most new browsers will handle things gracefully:

button:focus { 
outline: 3px solid #333;
outline-offset: 3px;
}
button:focus:not(:focus-visible) {
outline: none;
outline-offset: 0;
}
<button>A test</button>
<button>Another test</button>

Differentiate between :focus via tab-key and :focus via click in CSS

The :focus pseudo-class does not discriminate based on how the element entered focus in the first place. So indeed, this is not possible with just CSS. At the very least you'd need to annotate the element on focus via an event handler.

The :hover and :active pseudo-classes won't be of any help here since the former only applies when the mouse pointer is on the element and the latter only applies when the mouse button is down, i.e. neither state persists the way :focus does, since an element remains in focus even after the mouse pointer has left the element, making it indistinguishable from an element that received focus via tabbing.

outline only on tab focus

What you want to do is to add an event listener to the keyboard and mouse press.
If it's tab keypress then add a CSS class that would outline the desired elements.

function handleFirstTab(e) {
if (e.keyCode === 9) {
document.body.classList.add('user-is-tabbing');

window.removeEventListener('keydown', handleFirstTab);
window.addEventListener('mousedown', handleMouseDownOnce);
}
}

function handleMouseDownOnce() {
document.body.classList.remove('user-is-tabbing');

window.removeEventListener('mousedown', handleMouseDownOnce);
window.addEventListener('keydown', handleFirstTab);
}

window.addEventListener('keydown', handleFirstTab);


Related Topics



Leave a reply



Submit