Testing a "Back to top" pattern
A link directly after the main heading
You can test the "Back to top" link at the bottom of this page.
The basics
The accessible version of this pattern is simple. It just needs a fragment link, an id, and tabindex="-1" so the heading can receive focus.
<h1 id="top" tabindex="-1">Main heading</h1>
...
<p><a href="#top">Back to top</a></p>
The CSS provides a visible focus indicator when the heading receives focus:
h1[tabindex="-1"]:focus {
outline: 3px solid red;
outline-offset: 4px;
}
That is the basic pattern. It does not need JavaScript, and it keeps the viewport position, keyboard focus and the screen reader's reading position together. Keeping these aligned is an important part of making the pattern work well.
This test page adds smooth scrolling to that baseline, which is why it includes a small amount of JavaScript. The sections below look at the additional features and the trade-offs involved in each one.
Required
- Use a real fragment link, such as
<a href="#top">. This provides the basic behaviour and continues to work without JavaScript. - Point it at a meaningful target, such as the first
<h1>, and give that target anid. This gives people a clear indication of where they have landed. - Add
tabindex="-1"to the target if it cannot already receive focus. Without it, the viewport moves but focus does not, which can cause problems for screen reader and keyboard users. - Keep a clear focus indicator on the target for as long as it has focus. This shows where focus has moved and remains useful if someone looks away and then returns to the page.
- Add
scroll-margin-topif anything is fixed to the top of the viewport, so the target is not hidden underneath it. This relates to WCAG 2.4.11 Focus Not Obscured.
Good practice
These are not strict requirements, and they do not require JavaScript.
Highlight the destination
The focus ring shows where focus is. A brief flash can provide an additional cue that something has moved. Because Back to top can replace several screens of content at once, the destination may look quite different from what the user was just viewing.
A static focus ring can still take time to find, particularly for keyboard users and people using high zoom with only a small part of the page visible.
With an instant jump, focus and the visual change happen at the same time, so this cue only needs a few lines of CSS:
h1[tabindex="-1"]:focus {
animation: flash 1s ease-out;
}
@keyframes flash {
from { background-color: #ff0; }
to { background-color: transparent; }
}
- Keep the flash separate from the focus ring. It supplements the ring rather than replacing it, because once the flash has faded it can no longer show where focus is.
- Check the contrast of the flash colour against the heading text.
#ff0works well behind near-black text, but may be less effective behind a mid-grey heading. - Turn the flash off under
prefers-reduced-motion: reduce, while keeping the focus ring. - A single one-second fade is not a flashing hazard. WCAG 2.3.1 Three Flashes or Below Threshold is concerned with three or more flashes per second, so this cue is well below that threshold.
The control itself
- Name the destination rather than the motion. "Back to top" describes where the user will go, while "Scroll to top" describes the visual effect. An icon-only control still needs an accessible name.
- Keep the link's position in the DOM consistent with its visual position, so the tab order and reading order make sense.
- Keep the link at the content-start edge. Controls placed at the far edge of the viewport can be missed by people using magnification, who may only see part of the page at a time.
- Do not let a CSS reset remove the outline from
[tabindex="-1"]. This is a common reset rule and can remove the visible focus indicator.
Optional: smooth scrolling
Smooth scrolling only needs a small amount of CSS and can help show how far the page has moved, rather than using an instant jump.
- Set
scroll-behavior: smoothon the root element. - Honour
prefers-reduced-motion: reduceby switching back to an instant move.
One thing to consider before adding it: smooth scrolling separates the moment focus moves from the moment the target comes into view. On its own, this is not necessarily a problem. However, if it is combined with the focus flash above, the flash may begin while the target is still off screen. The next section looks at how to coordinate the two.
Optional: coordinating the cue with smooth scrolling
The script on this page delays the yellow flash until the smooth scrolling has finished. If you are not combining the flash with smooth scrolling, you do not need this script.
- Trigger the animation with a class rather than
:focus, so it can start when the target comes into view rather than when it receives focus. - Use the document
scrollendevent where supported, with a timeout as a fallback for browsers that do not support it. - Check whether any scrolling will actually occur, such as when the page is already at the target or is shorter than the viewport. In these cases, show the cue immediately rather than waiting for a timeout.
- Focus the target with
preventScroll: true, so moving focus does not jump to the target before the smooth scroll begins. - Keep the visual destination and the focus destination the same. If the viewport scrolls to one place while focus moves somewhere else, the focused element may end up off screen.
- Limit the script to "Back to top" links. Avoid intercepting every same-page fragment link or adding
tabindex="-1"to unrelated targets. - Allow modified clicks and links with another browsing target to keep their normal browser behaviour.
- Update the URL fragment when the link is activated, preserving the addressable
#topstate without adding duplicate history entries. - Restore focus to the link when the user presses the browser Back button. Because the script changes the link's default behaviour, it also needs to manage the resulting focus behaviour.
- No additional handling is needed on initial page load. Arriving at
page.html#topdirectly is normal fragment navigation, which the browser can handle.