Testing Support for :focus-visible

The new :focus-visible CSS selector lets us remove unsightly focus rings that often result in developers adding this to their stylesheets:

/* Please don't do this */
button:focus {
  outline: none;
}

While unsightly to mouse users, a clear focus indicator is essential for proper accessibility. How else will keyboard users, for example, know which element has focus?

Fortunately, :focus-visible gives us a way to make everyone happy by only applying focus styles when the user is interacting with a keyboard.

button:focus-visible {
  outline: dashed 2px orange;
}

It's currently available in all browsers except Safari. Well, it is available in Safari Technology Preview, but it's buried under the experimental features menu and it's not clear when it will land in mainstream Safari.

There's a polyfill you can use in the meantime but, since you can't polyfill pseudo selectors, it applies a focus-visible class instead. It's still useful if you really want this behavior, though!

Detecting Support #

If you're trying to figure out how to feature detect :focus-visible, you might be surprised to learn that there isn't an obvious way to do it — but there is a way!

function hasFocusVisible() {
  const style = document.createElement('style');
  let isSupported;

  try {
    document.head.appendChild(style);
    style.sheet.insertRule(':focus-visible { color: inherit }');
    isSupported = true;
  } catch {
    isSupported = false;
  } finally {
    style.remove();
  }

  return isSupported;
}

The secret sauce is in CSSStyleSheet.insertRule(), which will throw an error if you pass in a style it doesn't understand.

You can use it like this.

if (hasFocusVisible()) {
  // Supported!
} else {
  // Not supported
}

This can be pretty helpful if you're building something and you want to provide different behaviors depending on the browser's support for :focus-visible.