DEV Community

Cover image for Focus-visible: The Unsung Hero
Drew Clements
Drew Clements

Posted on

Focus-visible: The Unsung Hero

You find a lot of edge cases building UI's. Accessibly opening a modal, scroll-lock, strange focus states, etc..., and the solution to those edge cases aren't always apparent.

Well, I just found one that I already knew the answer too but forgot- so I'm writing this article to cement in my head and share the knowledge with all of you!

What is :focus-visible?

According the MDN, :focus-visible is a pseudo-class that applies while an element matches the :focus pseudo-class and determines vie heuristics that the focus should be made evident on the element.

In human speak? The browser determines if the :focus came from a click event or keyboard event and applies the styles accordingly. In most cases, that means that it won't apply the :focus styles from a click event.

That means that you give your keyboard users the focus styles they may want/need/desire without having a potentially jarring user experience for your mouse users.

What was the problem?

The issue I ran into was that I needed an element to have focus styles for accessibility's sake- but I didn't need some of those focus styles to apply when it was clicked.

To be even more clear, the hover and focus styles are the same. I wanted the hover styles to apply on hover but I didn't need the focus styles to linger after the element was clicked.

.logo-link:hover,
.logo-link:focus,
.initial-display {
  .logo-text {
    @apply translate-y-0;
  }
  .logo-type {
    @apply opacity-100;
  }
  .logo-tagline {
    @apply opacity-80;
  }
}
Enter fullscreen mode Exit fullscreen mode

You can see there wasn't much to the styles, but the client was adamant about this user experience.

The Solution

The answer to this problem was only 8 characters long!

Adding -visible to the end of the :focus pseudo-class was all it took.

.logo-link:hover,
.logo-link:focus-visible,
.initial-display {
  .logo-text {
    @apply translate-y-0;
  }
  .logo-type {
    @apply opacity-100;
  }
  .logo-tagline {
    @apply opacity-80;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now I can see clearly

I was absolutely ready to go town with javascript by using a click event to force .blur() and be as strong-handed as I needed to be to make it work.

I mentioned earlier that I knew this answer, but that I didn't come to this solution on my own. A co-worker suggested :focus-visible and it immediately came rushing back.

I ran into this exact problem a couple of months and the fix was the exact same thing!

Can I Use

There is one caveat to :focus-visible, and that is that it doesn't have full browser support yet.

You can see in the image below that it has decent coverage of around 70%, but it has 0 support in Safari, so you'll have to be sure to cover all of your bases and add in some polyfills.

A chart showing browser support for the pseudo-class focus-visible

Discussion (2)

Collapse
link2twenty profile image
Andrew Bone

A pattern I've been using a lot recently is use a couple of @supports to use the default focus in IE and fallback to :focus in browsers where :focus-visible is not supported.

@supports not (-ms-high-contrast: none) {
  button:focus {
    outline: none;
    box-shadow: inset 0 0 0 1px black,
      inset 0 0 0 3px white,
      inset 0 0 0 4px black;
  }
}

@supports selector(:focus-visible) {
  button:focus:not(:focus-visible) {
    box-shadow: initial;
  }
}
Enter fullscreen mode Exit fullscreen mode

Collapse
drewclem profile image
Drew Clements Author

I love this. Adding it to the toolbelt!