Find the full post on fotis.xyz
:focus-visible is a standard way of only showing focus styles for keyboard and focus-based modalities. When using it, however, you must take care to not remove :focus altogether, where :focus-visible is not supported.
With CSS, we can achieve progressive enhancement of :focus to :focus-visible:
/* Styles where only focus is supported */
button:focus,
a[href]:focus {
outline: 2px solid blue;
}
/* Reset styles where focus-visible is supported (we'll add them back in the next block) */
button:focus:not(:focus-visible),
a[href]:focus:not(:focus-visible) {
outline: none;
}
/* The final, focus-visible styles. You could elect to make this even more obvious,
if stakeholders or whomever is pushing against focus styles this week are now off your back.
*/
button:focus-visible,
a[href]:focus-visible {
outline: 2px solid blue;
}
If :focus-visible is not supported, nothing bad happens; the styling is still there. As more browsers gain support, the :focus-visible statements will be in use.
Read more about :focus-visible on MDN
This mixin uses a feature of Sass for passing arguments to content blocks. This feature is, at the time of writing, only supported in Dart Sass (sass
on npm). A version of the mixin that does not have the generic version (which requires that feature) is available in the focus_legacy.scss file.
These Sass mixins help achieve that declaration, for multiple selectors.
There are three separate mixins, two focusing on common use cases, and a fully custom one:
- focusVisible: a generic focusVisible, giving you "slots" to decide what to render for :focus, :focus reset, and :focus-visible
- focusVisibleOutline: a focusVisible, which only resets the outline. If you don't need more custom resets, use this one!
- focusVisibleBoxShadow: a focusVisible, which only resets the box-shadow. Might be easier if you only set/reset box-shadow.
For anything more custom, I recommend you write out the CSS long-hand, or create another mixin on top. Super-customisable, very generic mixins are a pain to maintain :D
Use focusVisibleOutline:
@include focusVisibleOutline("button", "a[href]") {
outline: 2px solid blue;
}
Use focusVisibleBoxShadow:
@include focusVisibleBoxShadow("button", "a[href]") {
box-shadow: 0 0 0 4px blue;
}
Use focusVisible:
This is the most customisable version of the mixin, where the mixin defers to the caller, with an argument representing the slot of content being rendered. The key thing here is then checking which slot is being rendered. For example, you can reset box-shadow, border, or anything else. This is how focusVisibleOutline and focusVisibleBoxShadow are implemented!
@include focusVisible using ($slot) {
@if $slot == focus {
outline: 2px solid transparent;
box-shadow: 0 0 0 4px blue;
}
@if $slot == focusReset {
box-shadow: none;
}
@if $slot == focusVisible {
box-shadow: 0 0 0 4px blue;
}
}