Problem

Default :focus behavior is bad.

Focus rings can be confusing for users with pointing devices (e.g. mouse, touch screen) because it implies a sense of persistence when showing up right after a click or a touch event.

Click me!

Try clicking and realize how bad it is.

Designers then need to make the :focus state obvious enough for keyboard users but at the same time, keeping it unobtrusive for mouse users.

This usually involves a compromise. Some, even remove the default focusing behavior entirely which is terrible for accessibility.

Removing the :focus outline is like removing the wheelchair ramp from a school because it doesn’t fit in with the aesthetic.

David Gilbertson

Solution

Ideally, the focus ring should only show up when the user intends to use the keyboard.

We need a better default browser behavior. In cases like this, it’s always a good idea to check if there are existing or upcoming browser specifications that solve our problem.

Luckily for us, there’s one: Introducing :focus-visible 🎉.

Specification

TLDR; :focus-visible is the keyboard-only version of :focus .

Also, the W3C proposal mentions that :focus-visible should be preferred over :focus except on elements that expect a keyboard input (e.g. text field, contenteditable).

Browser Support

At the time of writing, only Firefox supports :focus-visible natively. But the good news is that there’s an excellent polyfill available for us. Here’s a demo if you’d like to see it for yourself before we dive into the actual implementation details.

Implementation

Installation

via NPM

npm install --save focus-visible

require ( 'focus-visible' )

via UNPKG

Styling

The polyfill adds a .focus-visible class on elements that match the :focus-visible state, so styling is pretty straightforward.

*:focus:not(.focus-visible) { outline : none ; } .focus-visible { outline : lightgreen solid 2px ; }

Click and focus me!

Try clicking, then tabbing (or hitting the Shift key) to see improved behavior.

⭐️ Bonus tip: a better alternative to outline

*:focus { outline : none ; } .focus-visible { box-shadow : 0 0 0 2px lightgreen ; }

Better looking!

Ahh, rounded corners.