Last month, Heather Migliorisi looked at the accessibility of Smooth Scrolling. In order to do smooth scrolling, you:
- Check if the clicked link is #jump link
- Stop the browser default behavior of jumping immediately to that element on the page
- Animate the scrolling to the element the #jump link pointed to
Stopping the browser default behavior is the part that is problematic for accessibility. No longer does the #jump link move focus to element the #jump link pointed to. So Heather added a #4: move focus to the element the #jump link pointed to.
But moving focus through JavaScript isn’t possible on every element. Sometimes you need to force that element to be focusable, which she did through setting tabindex="-1"
.
We since updated our snippet to do this more-accessible handling of focus. Since then, we’ve heard from quite a few folks about a new side effect to the updated snippet:
After the smooth scrolling, the headers that were scrolled to now have the default blue glowing outline that links have by default. Even if we don’t always love the blue glowing outline (the style varies by user agent, it might be a dotted outline, for example) we know better to remove it.
There is even a website dedicated to that sentiment:
You can change the focus style if you like, but it’s good accessibility to have a distinct focus style for elements that are in focus. I can even imagine an argument for leaving the focus styles alone.
But but but
Headers aren’t focusable elements.
They aren’t “interactive” elements. We aren’t used to seeing focus styles on non-interactive elements. But now, all the sudden, because we used a smooth scrolling technique from the internet, we’re getting them. That tabindex="-1"
technique to allow focus is causing it.
I reached out to Heather to ask her about this.
The
:focus
styling for headings and other non-interactive elements can be removed/left off. It’s really up to the folks behind the creative site if they want it or not there. In some cases, I’ve removed it, in others I’ve styled it. It just depends.
To my surprise, it’s OK to remove focus styles in the case that the element you’re force-focusing isn’t interactive. That last point is important, and Heather clarified:
As long as the headings are not links… ;)
So if you really hated the focus styles, you could do:
h1:focus,
h2:focus,
h3:focus,
h4:focus,
h5:focus,
h6:focus {
outline: 0;
}
You’re safe there. Even in the cases of <a><h3></h3></a>
or <h3><a></a></h3>
, the link still has focus styles.
It’s not just headers
Jump links can point to any element, and there are lots of non-focusable-by-default elements. Another classic technique in this situation is the “yellow fade” technique. Let’s say instead of linking to headers, we were linking to <section>
s. When any section comes into focus, we could style it like this:
section:focus {
outline: 0;
animation: yellowFade 3s forwards;
}
@keyframes yellowFade {
from { background: lightYellow; }
to { background: white; }
}
Which gives us:
Which is an adaptation of something that was often used for :target.
I usually use something like the below for removing the focus on non-interactive elements (the only elements I give a negative tabindex to):
// Prevent a focus outline appearing on elements that should only be given focus
// via Javascript.
*[tabindex="-1"] {
outline: none;
}
Why not using default
:target
selector and leave focus for interactive elements?I’m skeptical that it’s ok to remove focus rings from non-interactive elements. The focus ring serves a purpose beyond just indicating that something is interactive. It helps keyboard users keep track of the focus order. It’s confusing to these users when an element comes into focus with zero visual indication. Imagine that you’re a keyboard user tabbing down a page, and you’ve got three or four of these focusable headers in a row. You tab 3+ times and nothing is happening. At this point, you might reasonably assume that you’re caught in a focus trap somewhere, give up, refresh, etc. Not a good experience.
This is not a problem, because
[tabindex="-1"]
elements don’t participate in the tab order. All that attribute does is it makes the element focusable via JavaScript.I see Jon beat me to the punch, but yes for keyboard accessibility we should preserve the element focus so the user always know where they are. It’s also part of the WCAG requirements (as I remember). Now, what I’ve done for all accessible sites is add Javascript to indicate where the interaction is coming from:
el.addEventListener('mousedown', () => {
el.setAttribute('data-focus-method', 'mouse');
});
el.addEventListener('blur', () => {
el.removeAttribute('data-focus-method');
});
This way I can remove all (or most of) the outlines when the user’s interacting via a mouse. In the given example, probably have to set the attribute on a higher dom.
Thank you for this update! It was super timely and kept me from breaking accessibility.