Implementing focus-visible Effectively
Note 1: The behavior of buttons remains consistent across all 3 options described below, with no focus ring displayed on click. However, the default behavior of selects and inputs varies slightly. Option 3 is the only one that uniformly removes focus rings around buttons, inputs, and selects. It is important to compare all approaches thoroughly to understand their implications.
Note 2: The order of CSS rules is crucial due to the cascading nature of CSS.
Note 3: While using the `focus-visible` approach addresses many accessibility concerns, there are still some issues to consider. Until browsers provide a configuration option for users to decide when visible focus rings should be displayed, `focus-visible` may pose accessibility challenges. Nonetheless, it is a better alternative than the detrimental `:focus {outline:none}` method mentioned in other responses. Refer to the "A note about accessibility" section at the end of this answer for additional information.
OPTION 1: Utilize the :focus-visible pseudo-class
The :focus-visible pseudo-class can eliminate outlines and focus rings on buttons and various elements for users not navigating via keyboard (e.g., through touch or mouse click).
Warning 2: Although widely supported by modern browsers, the :focus-visible pseudo-class does not work on certain fringe browsers as of 2021. For backward compatibility, the Javascript polyfill discussed in option 2 provides a close alternative. However, caution must be exercised as Polyfill.io ceased to be a reliable source after June 2024, as per SanSec research.
/**
* Remove focus styles for non-keyboard focus.
*/
:focus:not(:focus-visible) {
outline: 0;
box-shadow: none;
}
/**
* Cross-browser styles for explicit focus via
* keyboard-based (e.g., Tab navigation) or
* the .focus-visible utility class.
*/
:focus,
.focus-visible:focus:not(:focus-visible) {
outline: 0;
box-shadow:
0 0 0 .2rem #fff,
0 0 0 .35rem #069;
}
<h3>Defaults:</h3>
<button>Foo</button>
<input type="button" value="Bar"/>
<select><option>Baz</option></select>
<input type="text" placeholder="Qux"/>
<textarea placeholder="Quux" rows="1"></textarea>
<h3>Force focus on click:</h3>
<button class="focus-visible">Foo</button>
<input class="focus-visible" type="button" value="Bar"/>
<select class="focus-visible"><option>Baz</option></select>
<input class="focus-visible" type="text" placeholder="Qux"/>
<textarea class="focus-visible" placeholder="Quux" rows="1">
</textarea>
OPTION 2: Employ a .focus-visible polyfill
This solution utilizes a regular CSS class instead of the aforementioned pseudo-class and offers broader browser support as of 2021. It necessitates adding either one or two JavaScripts to your HTML document: one for the official focus-visible polyfill and the other for older browsers lacking classList support.
Note: In Chrome, the polyfill may handle selects differently compared to the native :focus-visible pseudo-class.
/**
* Cross-browser focus ring for explicit focus
* via keyboard-based (e.g., Tab navigation) or
* the .focus-visible utility class.
*/
:focus {
outline: 0;
box-shadow:
0 0 0 .2rem #fff,
0 0 0 .35rem #069;
}
/**
* Remove focus ring for non-explicit scenarios.
*/
:focus:not(.focus-visible) {
outline: 0;
box-shadow: none;
}
<h3>Defaults:</h3>
<button>Foo</button>
<input type="button" value="Bar"/>
<select><option>Baz</option></select>
<input type="text" placeholder="Qux"/>
<textarea placeholder="Quux" rows="1"></textarea>
<h3>Force focus on click:</h3>
<button class="focus-visible">Foo</button>
<input class="focus-visible" type="button" value="Bar"/>
<select class="focus-visible"><option>Baz</option></select>
<input class="focus-visible" type="text" placeholder="Qux"/>
<textarea class="focus-visible" placeholder="Quux" rows="1">
</textarea>
<!-- place this code just before the closing </html> tag -->
<script src="https://cdnjs.cloudflare.com/polyfill/v2/polyfill.js?features=Element.prototype.classList"></script>
<script src="https://unpkg.com/focus-visible"></script>
OPTION 3: Implement global key-navigation vs. mouse-navigation state
An alternate approach to focus-visible involves disabling outlines on `mousemove` events and enabling them on `keydown -> "Tab"`. Unlike specifying which elements should not display an outline, this method requires identifying which elements should have one.
document.addEventListener("mousemove", () =>
document.body.classList.remove("focus-visible")
);
document.addEventListener("keydown", ({key}) =>
(key === "Tab") && document.body.classList.add("focus-visible")
);
/**
* Cross-browser focus ring for explicit focus
* via keyboard-based (e.g., Tab navigation) or
* the .focus-visible utility class.
*/
:focus {
outline: 0;
box-shadow:
0 0 0 .2rem #fff,
0 0 0 .35rem #069;
}
/**
* Remove focus ring for non-explicit scenarios.
*/
body:not(.focus-visible) :focus:not(.focus-visible) {
outline: 0 !important;
box-shadow: none !important;
}
<h3>Defaults:</h3>
<button>Foo</button>
<input type="button" value="Bar"/>
<select><option>Baz</option></select>
<input type="text" placeholder="Qux"/>
<textarea placeholder="Quux" rows="1"></textarea>
<h3>Force focus on click:</h3>
<button class="focus-visible">Foo</button>
<input class="focus-visible" type="button" value="Bar"/>
<select class="focus-visible"><option>Baz</option></select>
<input class="focus-visible" type="text" placeholder="Qux"/>
<textarea class="focus-visible" placeholder="Quux" rows="1">
</textarea>
Insights on Accessibility
Eliminating all focus rings like `:focus { outline: none; }` or `:focus { outline: 0; }` poses a known accessibility challenge and is strongly discouraged. Some accessibility advocates suggest never removing a focus ring outline but ensuring every element has a `:focus` style — be it `outline` or `box-shadow`, provided it is styled appropriately.
Moreover, some members of the accessibility community advise against implementing `:focus-visible` until all browsers offer a user preference for controlling focus visibility settings universally. I differ in opinion from this stance, hence presenting a solution that strikes a balance between design aesthetics and accessibility considerations. As of 2022, Chrome allows users to set focus visibility preferences, while Firefox lacks this feature.
Resource:
Demo: