James McGrath

Focus Styling: From :focus to :focus-visible

Published on Jul 16, 2025
Reading time: 2 minutes

Initially, browsers universally applied focus styles via the :focus pseudo-class to all focusable elements, typically as a focus ring. This default user-agent style ensured every link and button displayed a focus ring when focused. Developers often overrode this default for visual consistency.

However, always-visible focus rings on every element were considered visually distracting and detrimental to UX. Users found the constant focus indicators overwhelming.

Modern browsers now selectively apply focus rings based on heuristics. Focus styles are crucial for keyboard navigation and programmatic focus management (JavaScript). However, when users directly interact using mouse or touch, focus rings are often redundant. Form elements remain a key exception where clear focus indication remains important.

The :focus-visible pseudo-class addresses this by respecting the browser's intelligent focus indication while allowing style customization. This provides a more nuanced approach, but :focus styles remain necessary for older browser compatibility. However you can get away with just providing :focus-visible styles if you can absolutely guarantee that all your users are on a device that supports :focus-visible , otherwise you will need to use a polyfill or a CSS fallback method.

For broad browser support, consider these fallback strategies:

Method 1: @supports Feature Query

CSS

*:focus { /* Default focus styles */ }
@supports selector(:focus-visible) {
  *:focus { /* Undo default styles in modern browsers */ }
  *:focus-visible { /* Reapply for :focus-visible */ }
}

Method 2: :not(:focus-visible) Fallback

CSS

*:focus { /* Default focus styles */ }
*:focus:not(:focus-visible) { /* Undo default styles in :focus-visible browsers */ }
*:focus-visible { /* Styles for :focus-visible browsers */ }

While less explicit, you could also use:

CSS

*:focus-visible { /* Focus styles for :focus-visible */ }
@supports not selector(:focus-visible) {
  *:focus { /* Focus styles for older browsers */ }
}

Methods 1 & 2 are clearer in demonstrating the fallback approach, explicitly showing default :focus styles being refined for :focus-visible browsers.