cMarius
cMarius

Reputation: 1132

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

It's long been common practice to remove outline via CSS outline: none; from interactable elements such as buttons, since most of the time it looks off brand, but it hinders accessibility and makes navigating websites harder for people with disabilities that rely on keyboard navigation.

The problem is I have yet to find an easy way to differentiate the source of focus events between mouse/keyboard, and clicking as well as keyboard tabbing elements will trigger the focus state.

I know this issue has always been kind of a hot topic, but most sources are several years old, so I will show the main 4 methods I've found on how to achieve keyboard only focus, each with it's own pros and cons:

  1. The "cheating" method, some websites such as Target.com have the same styles for hovering and focusing, this design choice gave them the option of not needing to remove the focus outline at all.

Pros: Goes around the problem.

Cons: Limited in design options.

button {
  width: 180px;
  height: 60px;
  background: #999;
  border: none;
}

button:focus,
button:hover { 
  outline: 5px solid green; 
}
<button>Click or Tab me!</button>

  1. The JS solution, websites such as Apple.com use ally.js to add data-focus-method attributes to their interactable elements. what-input is also quite well known and does a great job, together with various other polyfills.

Pros: Functions properly across all browsers, can be implemented regardless of design.

Cons: Requires extra http requests to fetch .js files, JS is more taxing on performance compared to other solutions.

ally.style.focusSource().current()
button {
  width: 180px;
  height: 60px;
  background: #999;
  border: none;
  outline: none;
}

html[data-focus-source="key"] button:focus {
  outline: 5px solid green;
}
<button>Click or Tab me!</button>
<script src="https://cdn.jsdelivr.net/ally.js/1.4.1/ally.min.js"></script>

  1. :focus-within the holy grail of elegant solutions. :focus-visible is basically the keyboard-only version of :focus and has slowly been made available on most modern browsers (previously :focus-ring).

Pros: CSS solution, easy on performance, easy to implement.

Cons: Browser support is better but still not ideal with Safari not yet on board.

EDIT: :focus-visible HAS BEEN ENABLED BY DEFAULT BY SAFARI AS OF MARCH 14 2022

button {
  width: 180px;
  height: 60px;
  background: #999;
  border: none;
  outline: none;
}

button:focus-visible {
  outline: 5px solid green;
}
<button>Click or Tab me!</button>

  1. The Roman Komarov tabindex="-1" hack shows that we can differentiate focus styles by adding a span inside our button elements (technically valid HTML) then giving that span tabindex="-1" and different focus styling.

Pros: No JS involved, seems to work well across all browsers.

Cons: Requires spamming spans inside all buttons and links on the website, the need to move padding from buttons/links to the inner span, requires adjusting various tracking tags such as gtm.

button {
  width: 180px;
  height: 60px;
  background: #999;
  border: none;
  outline: none;
}

button > span {
  display: flex;
  justify-content: center;
  height: 100%;
  align-items: center;
}

button:focus {
  outline: 0;
}

button:focus > span {
  outline: 5px solid green;
}

[tabindex="-1"]:focus {
  outline: none !important;
}
<button>
  <span tabindex="-1">Click or Tab me!</span>
</button>

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?

Upvotes: 5

Views: 2073

Answers (2)

Stefany Newman
Stefany Newman

Reputation: 524

I can't remember it off the top of my head, but there is a method using JS, basically on mouse click you remove the outline. I did it a year ago, I am just foggy on the details.

Upvotes: 1

GrahamTheDev
GrahamTheDev

Reputation: 24825

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>

The downsides to this solution?

Safari will still show focus indicators on click.....and that is OK!

Safari will eventually catch up and use :focus-visible. Until then the above fiddle is the simplest way to ensure that browsers that do support :focus-visible behave as expected and browsers that don't support it fall back to providing focus indicators on click.

But it has to be perfect

Then I would just use a polyfill that is conditionally loaded based on support.

This answer details how to detect support for :focus-visible

At which point you could conditionally load a :focus-visible polyfill (which is 3.5kb gzipped).

I personally don't like this solution as you have to change all of your CSS....but I am not bothered by focus indicators showing on click as much as most people so I might be biased!

Upvotes: 3

Related Questions