Tutorials / / 4 min read
How to create a scroll to top button with Tailwind CSS: JavaScript and Alpine.js
Build a scroll to top button with Tailwind CSS, two ways: vanilla JavaScript or Alpine.js. It fades in after you scroll and smoothly returns to the top.
Remember the scroll to top button we did with only Tailwind CSS? Today we are building it twice more, first with vanilla JavaScript, then with Alpine.js, so you can pick whichever fits your stack.
What is a scroll to top button?
A scroll to top button appears near the bottom corner of the page once the user scrolls down, and one click smoothly returns them to the top. It’s a small touch that makes pages with extensive content much nicer to navigate.
Use cases
- Blogs and news sites: long articles deserve a quick way back to the headlines.
- E-commerce: endless product lists make the return trip painful without one.
- Documentation: jump back to the table of contents in one click.
- Landing pages: after a long scroll, bring visitors back to the main call to action.
The markup
The button is hidden by default and will only be shown when the user scrolls down.
id="scrollButton": identifies the button wrapper in JavaScript.fixed bottom-6 right-6 z-50: pins the button to the bottom right corner, above the content.hidden opacity-0 translate-y-2: the button starts hidden, transparent, and slightly shifted down.transition-opacity duration-300 transform: animates the button as it fades in and out over 300 milliseconds.
<div
id="scrollButton"
class="fixed z-50 hidden opacity-0 bottom-6 right-6 transition-opacity duration-300 transform translate-y-2"
>
<button>scroll to top</button>
</div>The script
document.addEventListener("scroll", () => {: watches the page scroll.if (window.scrollY > 100) {: once the user has scrolled past 100 pixels, removeshidden,opacity-0, andtranslate-y-2and addsopacity-100andtranslate-y-0to fade the button in.- On the way back up it reverses those classes, and a
setTimeoutre-addshiddenafter 300 milliseconds so the fade-out can finish first. document.getElementById("scrollButton").addEventListener("click", () => {: scrolls the page back up withwindow.scrollTo({ top: 0, behavior: "smooth" }).
document.addEventListener("scroll", () => {
const scrollButton = document.getElementById("scrollButton");
if (window.scrollY > 100) {
scrollButton.classList.remove("hidden", "opacity-0", "translate-y-2");
scrollButton.classList.add("opacity-100", "translate-y-0");
} else {
scrollButton.classList.add("opacity-0", "translate-y-2");
scrollButton.classList.remove("opacity-100", "translate-y-0");
setTimeout(() => {
if (window.scrollY <= 100) {
scrollButton.classList.add("hidden");
}
}, 300);
}
});
document.getElementById("scrollButton").addEventListener("click", () => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
});The Alpine.js version
Same component, no separate script: the state lives in x-data.
x-data="{ isVisible: false }": stores the visibility of the button.x-init="window.addEventListener('scroll', () => { isVisible = window.scrollY > 100; })": flips it once the user scrolls past 100 pixels.x-show="isVisible": shows and hides the button.- The
x-transition:enterandx-transition:leaveattributes fade and slide the button in and out over 300 milliseconds. title="Scroll to top"andaria-label="Scroll to top": describe the button’s purpose for accessibility.@click="window.scrollTo({ top: 0, behavior: 'smooth' })": smoothly scrolls back to the top.
Classes are removed for brevity and clarity within the code, but I’ll keep those classes relevant to the tutorial.
<div
x-data="{ isVisible: false }"
x-init="window.addEventListener('scroll', () => { isVisible = window.scrollY > 100; })"
class="fixed z-50 bottom-6 right-6"
x-show="isVisible"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 transform translate-y-2"
x-transition:enter-end="opacity-100 transform translate-y-0"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 transform translate-y-0"
x-transition:leave-end="opacity-0 transform translate-y-2"
>
<button
title="Scroll to top"
aria-label="Scroll to top"
@click="window.scrollTo({ top: 0, behavior: 'smooth' })"
>
scroll to top
</button>
</div>You can try the Alpine version separately: live demo and source code.
Which one should you use?
Alpine’s x-show and x-transition handle the fade declaratively, so pick that if Alpine is already loaded. The vanilla version trades a few class toggles for zero dependencies.
Conclusion
A visibility check, a smooth scroll, and a fade: that’s the whole button. And if you don’t need the fancy behavior at all, a plain anchor link and a bit of CSS can do the trick:
<a href="#top" class="scroll-to-top">Up</a>Hope you enjoyed this tutorial and have a great day!
/Michael Andreuzza