Delving Developer

How to create an accessible "back to top" button in React

Eddie Cunningham
Eddie Cunningham
5 min readReact.js
Cover Image for How to create an accessible "back to top" button in React

A "back to top" button is incredibly useful if you have a lot of content on your page. Sometimes, a user may want to navigate to other parts of the site but needs to scroll through everything that they've already looked at to do so. In this guide, we'll be going through the steps required to create your own accessible "back to top" button in React.

Getting set uplink

If you haven't already, set up your React project:

npx create-react-app my-app
cd my-app

Creating our componentlink

Let's go ahead and create our basic component. We're using the button element instead of an a because this is an action and not some form of navigation.

We'll also be adding the aria-hidden attribute to our SVG. We do this because the icon is purely decorative, and we want to hide it from assistive technology.

export default function BackToTop() {
  return (
    <button onClick={onClick}>
      <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 490 490"><path d="M437.2 178.7c12.8 12.8 12.8 33.4 0 46.2-6.4 6.4-14.7 9.6-23.1 9.6s-16.7-3.2-23.1-9.6L277.7 111.5v345.8c0 18-14.6 32.7-32.7 32.7s-32.7-14.6-32.7-32.7V111.5L99 224.9c-12.8 12.8-33.4 12.8-46.2 0s-12.8-33.4 0-46.2L221.9 9.6C228 3.4 236.3 0 245 0c8.7 0 17 3.4 23.1 9.6l169.1 169.1z"/></svg>
      <span className="sr-only">Back to top</span>
    </button>
  )
}

You'll also notice that we've added the physical "Back to top" text inside a span with a class name of sr-only. This is important for screen readers, but may be visually redundant within your design. There are multiple ways to hide the text, and there are also some pre-prepared classes if you're using libraries such as Tailwind. If you want to write your own CSS, it may look something like this:

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

Implementing the functionalitylink

We need to add an onClick event handler to our button element. When clicked, we are going to use the browser's native scrollTo method to send the user back to the top of the page. We'll pass an options object, where we set the top and left properties to 0 (the top left corner), and adding the behavior: 'smooth' property means that the scrolling will happen smoothly rather than instantly.

But wait...disaster! As it turns out, Internet Explorer doesn't support the options object is passed to the scrollTo method. We'll also need to add in a check to ensure that we call it in the way that works for each browser.

We're also going to look for the first focusable element, and store it against the focusableElement variable. Once the user has scrolled to the top of the page, we want to focus back to the earliest point in the document, akin to the first time the page loads.

const scrollOptions = {
  top: 0,
  left: 0,
  behavior: 'smooth'
}

const supportsNativeSmoothScroll = 'scrollBehavior' in document.documentElement.style

const scrollToTop = () => supportsNativeSmoothScroll ? window.scrollTo(scrollOptions) : window.scrollTo(scrollOptions.left, scrollOptions.top)

export default function BackToTop() {

  const onClick = () => {
    const focusableElement = document.querySelector('button, a, input, select, textarea, [tabindex]:not([tabindex="-1"])')
    scrollToTop()

    focusableElement.focus({
      preventScroll: true,
    })
  }

  return (
    <button onClick={onClick}>
      <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 490 490"><path d="M437.2 178.7c12.8 12.8 12.8 33.4 0 46.2-6.4 6.4-14.7 9.6-23.1 9.6s-16.7-3.2-23.1-9.6L277.7 111.5v345.8c0 18-14.6 32.7-32.7 32.7s-32.7-14.6-32.7-32.7V111.5L99 224.9c-12.8 12.8-33.4 12.8-46.2 0s-12.8-33.4 0-46.2L221.9 9.6C228 3.4 236.3 0 245 0c8.7 0 17 3.4 23.1 9.6l169.1 169.1z"/></svg>
      <span>Back to top</span>
    </button>
  )
}

Displaying the button on scrolllink

If you'd like to only show your "back to top" button as the user scrolls down the page, we'll need to add an event listener so that we can identify when the user has scrolled down the page. We can utilize React's useEffect to set it up. You'll also notice that we're returning a function within the effect that removes the listener. This is useEffect's built-in cleanup mechanism. Finally, we're passing an empty array as a second argument to the effect to let it know that there are no dependencies and to prevent it from running more than once.

Our onScroll function is quite simple. It checks whether the user has scrolled further than 300px, and if showButton is already false, will set it true. We can then use showButton to conditionally show our button!

  const [showButton, setShowButton] = useState(false)

  const onScroll = () => {
    // You can adjust this to show earlier/later
    if (!showButton && window.scrollY > 300) {
      setShowButton(true)
    } else {
      setShowButton(false)
    }
  }

  useEffect(() => {
    window.addEventListener('scroll', onScroll, { passive: true })
    return () => window.removeEventListener('scroll', onScroll)
  }, [])

And that's it! You should now have a "back to top" button that:

  • Supports all major browsers
  • Is accessible
  • Only displays when the user has scrolled down the page

Check out our completed code below:

import { useCallback, useEffect, useState } from 'react'

const scrollOptions = {
  top: 0,
  left: 0,
  behavior: 'smooth'
}

const supportsNativeSmoothScroll = 'scrollBehavior' in document.documentElement.style

const scrollToTop = () => supportsNativeSmoothScroll ? window.scrollTo(scrollOptions) : window.scrollTo(scrollOptions.left, scrollOptions.top)

export default function BackToTop() {
  const [showButton, setShowButton] = useState(false)

  const onClick = () => {
    const focusableElement = document.querySelector('button, a, input, select, textarea, [tabindex]:not([tabindex="-1"])')
    
    scrollToTop()

    focusableElement.focus({
      preventScroll: true,
    })
  }

  const onScroll = () => {
    if (!showButton && window.scrollY > 300) {
      setShowButton(true)
    } else {
      setShowButton(false)
    }
  }

  useEffect(() => {
    window.addEventListener('scroll', onScroll, { passive: true })
    return () => window.removeEventListener('scroll', onScroll)
  }, [])


  return (
    showButton && <button onClick={onClick}>
      <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 490 490"><path d="M437.2 178.7c12.8 12.8 12.8 33.4 0 46.2-6.4 6.4-14.7 9.6-23.1 9.6s-16.7-3.2-23.1-9.6L277.7 111.5v345.8c0 18-14.6 32.7-32.7 32.7s-32.7-14.6-32.7-32.7V111.5L99 224.9c-12.8 12.8-33.4 12.8-46.2 0s-12.8-33.4 0-46.2L221.9 9.6C228 3.4 236.3 0 245 0c8.7 0 17 3.4 23.1 9.6l169.1 169.1z"/></svg>
      <span>Back to top</span>
    </button>
  )
}