Unpoly lets you control whether a focused fragment shows a visible focus ring.
Because Unpoly often focuses new content, you may see focus outline appear in unexpected places. Try to resist an initial instinct to just remove focus rings globally using CSS. Focus rings are important for users of keyboards and screen readers to be able to orient themselves as the focus moves on the page. However, mouse and touch users often dislike the visual effect of a focus ring.
To help your CSS show or hide focus rings in the right situation, Unpoly assigns CSS classes to the elements it focuses:
.up-focus-visible
class..up-focus-hidden
class instead.Note
The web platform uses the
:focus-visible
pseudo-class to indicate focus ring visibility. However browsers often incorrectly apply:focus-visible
during script-driven navigations like Unpoly.Unpoly will try to force
:focus-visible
whenever it sets.up-focus-visible
, but can only do so in some browsers.
You may see unwanted focus rings that you inherited from a user agent stylesheet or from a CSS framework like Bootstrap. You can remove these outlines for most mouse and touch interactions, using CSS like this:
:focus:not(:focus-visible, .up-focus-visible),
.up-focus-hidden {
outline: none !important;
}
By default Unpoly removes an outline
CSS property from elements with an .up-focus-hidden
class.
Tip
CSS frameworks might render focus rings using properties other than
outline
. For example, Bootstrap uses abox-shadow
to produce a blurred outline.
When creating a new interactive component, you should make it focusable using the keyboard's Tab
key
by assigning a [tabindex]
attribute:
<span class="my-button" tabindex="0">
...
</span>
To show a focus ring for keyboard users only, use CSS like this:
.my-button {
&:focus-visible:not(.up-focus-hidden),
&.up-focus-visible {
outline: 1px solid royalblue;
}
}
You can set up.viewport.config.autoFocusVisible
to a function that decides if a given element should
get a .up-focus-visible
or .up-focus-hidden
class.
The default strategy is implemented like this:
up.viewport.config.autoFocusVisible = ({ element, inputDevice }) =>
inputDevice === 'key' || up.form.isField(element)
See up.event.inputDevice
for a list of values for the { inputDevice }
property.
You can replace or extend the default strategy. For example, this would generally use the default strategy, but also never show a focus ring on main elements:
let defaultVisible = up.viewport.config.autoFocusVisible
up.viewport.config.autoFocusVisible = (options) =>
defaultVisible(options) && !up.fragment.matches(options.element, ':main')