Scroll to top button: a React component I use in every project
I add a scroll-to-top button to almost every project. I got tired of writing it again each time, so I made one reusable version and I'm sharing it here. It works on a personal blog and in a large web app.
The design is based on Google Chrome's interface. It uses Framer Motion, so the button fades in when it appears and jumps when you hover over it.
Here is the code:
'use client'
import { useState, useEffect } from 'react'
import { motion } from 'framer-motion'
import { ChevronUpCircleIcon } from 'lucide-react'
const BackToTopButton = () => {
const [isVisible, setIsVisible] = useState(false)
const toggleVisibility = () => {
setIsVisible(window.scrollY > 500)
}
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
}
useEffect(() => {
window.addEventListener('scroll', toggleVisibility)
return () => window.removeEventListener('scroll', toggleVisibility)
}, [])
const jumpAnimation = {
y: [0, -10, 0, -10, 0],
transition: {
y: {
duration: 0.7,
ease: 'easeOut',
repeat: 1,
},
},
}
return (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: isVisible ? 1 : 0, scale: isVisible ? 1 : 0.8 }}
transition={{ duration: 0.4 }}
className="fixed bottom-16 right-5 z-50"
>
{isVisible && (
<motion.button
whileHover={jumpAnimation}
onClick={scrollToTop}
aria-label="Back to top"
>
<ChevronUpCircleIcon
size={40}
absoluteStrokeWidth={true}
strokeWidth={1.5}
/>
</motion.button>
)}
</motion.div>
)
}
export default BackToTopButton
How it works
'use client' marks this as a client component in Next.js. It needs this because it listens to scroll events and runs animations in the browser.
useState holds whether the button is visible. A scroll listener on window sets it to true when the page is scrolled more than 500 pixels. Change 500 if you want the button to show earlier or later.
scrollToTop calls window.scrollTo with behavior: 'smooth', so the page scrolls up instead of jumping to the top.
Framer Motion runs two animations. When the button appears, it fades in and scales from 0.8 to 1. On hover, it bounces up and down a few times.
The button has an aria-label, so screen readers announce it as "Back to top".
The icon is ChevronUpCircleIcon from Lucide React. You can replace it with any other icon.
How to use it
- Install the dependencies:
npm install framer-motion lucide-react
- Copy the
BackToTopButtoncomponent into your project. - Import it where you want it:
import BackToTopButton from './path/to/BackToTopButton'
function App() {
return (
<div>
{/* Your app content */}
<BackToTopButton />
</div>
)
}
To change it, adjust the scroll threshold, move the button with the Tailwind classes on the wrapper (bottom-16 right-5), swap the icon, or change the animation values.
If you change it in an interesting way, I'd like to hear about it.