August 2026

How to Add a Sticky Back-to-Top Button to Your Website With CSS and JavaScript

A well-crafted back to top button is one of those tiny UX details that can dramatically improve navigation on long pages, blogs, documentation sites, and product listings. In this tutorial, we will build a lightweight, accessible, and smooth-scrolling back to top button using nothing more than plain HTML, CSS, and a few lines of JavaScript. No libraries, no bloat. By the end of this guide, you will have a production-ready snippet you can drop into any project, including Express.js views, static sites, WordPress themes, or single-page applications. What Is a Back to Top Button? A back to top button is a small floating control, typically anchored to the bottom-right corner of the viewport, that scrolls the user back to the top of the page when clicked. It usually appears only after the user has scrolled down a certain distance, keeping the interface clean when it is not needed. When Should You Use One? Long-form content such as blog posts, guides, and documentation Infinite scroll or lazy-loaded feeds Product listing pages with heavy filtering Mobile layouts where scrolling up manually is tedious If your page fits in one or two viewport heights, you probably do not need one. Reserve it for pages taller than roughly four screens. Step 1: Add the HTML We will use a real <button> element rather than a <div> or <a>. This matters for accessibility and keyboard navigation. <button id=”backToTop” class=”back-to-top” aria-label=”Scroll back to top” title=”Back to top”> <svg xmlns=”http://www.w3.org/2000/svg” width=”24″ height=”24″ viewBox=”0 0 24 24″ fill=”none” stroke=”currentColor” stroke-width=”2″ stroke-linecap=”round” stroke-linejoin=”round” aria-hidden=”true”> <path d=”M18 15l-6-6-6 6″/> </svg> </button> Place this markup right before your closing </body> tag so it stays out of the main document flow. Step 2: Style It With CSS The CSS handles positioning, appearance, and the smooth fade transition when the button appears or disappears. .back-to-top { position: fixed; bottom: 2rem; right: 2rem; width: 48px; height: 48px; border: none; border-radius: 50%; background-color: #0d6efd; color: #ffffff; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); opacity: 0; visibility: hidden; transform: translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s; z-index: 999; } .back-to-top.is-visible { opacity: 1; visibility: visible; transform: translateY(0); } .back-to-top:hover, .back-to-top:focus-visible { background-color: #0b5ed7; outline: 2px solid #ffffff; outline-offset: 2px; } @media (prefers-reduced-motion: reduce) { .back-to-top { transition: opacity 0.2s ease; transform: none; } } Key CSS Choices Explained position: fixed keeps the button anchored while the user scrolls opacity + visibility creates a smooth fade without leaving an invisible click target focus-visible ensures keyboard users see a clear focus ring prefers-reduced-motion respects users who disable animations Step 3: Add the JavaScript We only need a small script to toggle the visible class and handle the scroll action. (function () { const button = document.getElementById(‘backToTop’); if (!button) return; const SCROLL_THRESHOLD = 300; // pixels let ticking = false; function updateButton() { if (window.scrollY > SCROLL_THRESHOLD) { button.classList.add(‘is-visible’); } else { button.classList.remove(‘is-visible’); } ticking = false; } window.addEventListener(‘scroll’, function () { if (!ticking) { window.requestAnimationFrame(updateButton); ticking = true; } }, { passive: true }); button.addEventListener(‘click’, function () { const prefersReducedMotion = window.matchMedia(‘(prefers-reduced-motion: reduce)’).matches; window.scrollTo({ top: 0, behavior: prefersReducedMotion ? ‘auto’ : ‘smooth’ }); }); })(); Why This Script Is Fast Uses requestAnimationFrame to throttle scroll events Uses a passive scroll listener so the browser can optimize scrolling performance Falls back to instant scroll when the user prefers reduced motion Wrapped in an IIFE to avoid polluting the global scope Accessibility Checklist A back to top button should be usable by everyone, not just mouse users. Here is what to verify before shipping: Concern Implementation Keyboard focus Use a native <button> element Screen readers Add aria-label=”Scroll back to top” Motion sensitivity Respect prefers-reduced-motion Focus after scroll Optionally move focus to the main heading Contrast Maintain at least 4.5:1 ratio for the icon Optional: Move Focus After Scrolling For screen reader users, scrolling visually to the top does not move the focus. You can improve this by focusing your main landmark after the scroll completes: button.addEventListener(‘click’, function () { window.scrollTo({ top: 0, behavior: ‘smooth’ }); const main = document.querySelector(‘main, h1’); if (main) { main.setAttribute(‘tabindex’, ‘-1’); main.focus({ preventScroll: true }); } }); Design Best Practices in 2026 Show it only when useful. Reveal after the user scrolls at least 300 to 500 pixels. Keep it small. Around 40 to 56 pixels wide, never dominating the viewport. Use a clear icon. An upward-pointing chevron or arrow is universally understood. Do not block content. On mobile, avoid overlapping call-to-action buttons or chat widgets. Match your brand. Use your primary color, but ensure contrast against varied backgrounds. Common Mistakes to Avoid Using an <a href=”#top”> without preventDefault, which pushes an entry into browser history Animating scroll with heavy JavaScript loops when window.scrollTo handles it natively Forgetting to test with keyboard-only navigation Placing the button over important interactive UI on small screens FAQ Do I need JavaScript for a back to top button? Not necessarily. A pure anchor link pointing to #top combined with scroll-behavior: smooth on the html element works without any JavaScript. However, JavaScript gives you control over when the button appears and how it behaves on reduced-motion systems. What is the ideal scroll distance before showing the button? Between 300 and 500 pixels works well for most sites. On very long articles, you might delay it further to avoid distraction during the first fold of reading. Is a back to top button good for SEO? Indirectly, yes. It improves dwell time and reduces friction on long pages, which can positively impact engagement metrics. It has no direct ranking factor, but better UX typically correlates with better rankings. Should the button appear on mobile? Yes, especially on mobile, where scrolling long articles is tedious. Just be careful not to cover sticky navigation bars, floating chat icons, or cookie banners. Can I use CSS scroll-behavior instead of JavaScript? Absolutely. Add html { scroll-behavior: smooth; } and link to an anchor at the top of the page. The trade-off is that you

How to Add a Sticky Back-to-Top Button to Your Website With CSS and JavaScript Read More »

How to Add a Reading Progress Bar to Your Blog With CSS and JavaScript

You’ve seen it on Medium, on major blogs, on documentation sites: a thin colored bar at the top of the page that fills up as you scroll down an article. It’s small, it’s subtle, and it dramatically improves the reading experience. In this tutorial, we’ll build a reading progress bar with CSS and vanilla JavaScript that you can drop into any blog, WordPress site, or custom project in under 5 minutes. No frameworks. No libraries. Just clean code you can copy, paste, and style however you like. What Is a Reading Progress Bar? A reading progress bar is a visual indicator, usually fixed at the top of the viewport, that reflects how far a user has scrolled through the main content of a page. It answers a simple but powerful question for your readers: how much is left to read? Benefits of adding one to your blog: Reduces bounce rate on long-form articles Sets clear expectations about article length Provides a subtle, modern UX touch Encourages users to keep scrolling Two Approaches: CSS-Only vs. JavaScript Before jumping into code, let’s compare the two main approaches so you can choose the right one for your project. Feature CSS-Only (animation-timeline) JavaScript Browser support Modern browsers only Universal Performance Excellent (native) Very good with throttling Flexibility Limited Full control Track only article content Yes Yes We’ll cover both. Start with the JavaScript version if you need broad support, or jump to the CSS-only version if you’re targeting modern browsers. Step 1: Add the HTML Markup The bar itself is just an empty div placed near the top of your body. Add this snippet right after your opening <body> tag: <div id=”reading-progress”></div> That’s it. The div will be styled and updated dynamically. Step 2: Style the Bar With CSS Here is the base CSS. It fixes the bar at the top, gives it height, and prepares the fill state: #reading-progress { position: fixed; top: 0; left: 0; height: 4px; width: 0%; background: linear-gradient(90deg, #ff6b6b, #f06595); z-index: 9999; transition: width 0.1s ease-out; } @media (max-width: 600px) { #reading-progress { height: 3px; } } Key styling choices explained: position: fixed keeps the bar visible while scrolling z-index: 9999 ensures it stays above sticky headers and modals transition smooths out the fill animation Media query reduces height on mobile for a lighter feel Step 3: The JavaScript Logic This is where the magic happens. Add this script before the closing </body> tag: <script> (function() { const bar = document.getElementById(‘reading-progress’); const article = document.querySelector(‘article’) || document.body; function updateProgress() { const rect = article.getBoundingClientRect(); const total = rect.height – window.innerHeight; const scrolled = -rect.top; const percent = Math.min(Math.max((scrolled / total) * 100, 0), 100); bar.style.width = percent + ‘%’; } let ticking = false; window.addEventListener(‘scroll’, function() { if (!ticking) { window.requestAnimationFrame(function() { updateProgress(); ticking = false; }); ticking = true; } }); updateProgress(); })(); </script> What the script does: Selects the progress bar and the article container Calculates the total scrollable distance within the article Determines how much of that distance has been scrolled Updates the bar width using requestAnimationFrame for smooth performance Note: the script targets the <article> element. This means the bar only fills while the user is inside the article, not while scrolling past headers or footers. If your theme doesn’t use an <article> tag, replace the selector with your content wrapper class, for example .entry-content or .post-content. Step 4: The CSS-Only Alternative If you only need to support modern browsers, you can skip JavaScript entirely using animation-timeline: scroll(). Here is the complete snippet: #reading-progress { position: fixed; top: 0; left: 0; height: 4px; width: 100%; background: linear-gradient(90deg, #ff6b6b, #f06595); transform-origin: 0 50%; animation: progress linear; animation-timeline: scroll(root); z-index: 9999; } @keyframes progress { from { transform: scaleX(0); } to { transform: scaleX(1); } } This uses the scroll-driven animations feature, which is supported in Chrome, Edge, and Opera. Firefox and Safari support is progressing, so treat this as progressive enhancement. How to Add It to WordPress There are three easy ways to integrate the code into a WordPress site. Option 1: Via a Child Theme Add the CSS to your child theme’s style.css Add the JavaScript to a new file called progress.js in your child theme Enqueue both in functions.php using wp_enqueue_style and wp_enqueue_script Insert the <div id=”reading-progress”></div> in your header.php Option 2: Using a Code Snippet Plugin Plugins like WPCode or Code Snippets let you inject the HTML div, CSS, and JS without editing theme files. Set the snippet to run only on single blog posts using their conditional logic. Option 3: Directly in the Block Editor Use a Custom HTML block at the top of your post template to insert the markup, then add the CSS via Appearance > Customize > Additional CSS. Mobile Behavior and Best Practices Mobile users represent the majority of blog traffic, so the bar should feel native on small screens. A few tips: Keep it thin. 3px is enough on mobile. Anything taller competes with your content. Avoid distracting animations. A simple width transition of 0.1s is enough. Respect reduced motion. Wrap animations in a @media (prefers-reduced-motion: no-preference) query. Test with sticky headers. If your header is sticky, make sure the bar sits above it or just below it, not overlapping the logo. Use accessible colors. The bar should have sufficient contrast against your header background. Styling Variations The base version uses a gradient, but you can go creative: Solid brand color for a minimal look Animated shimmer using a repeating linear gradient Bottom placement by changing top: 0 to bottom: 0 Vertical bar on the right edge, useful for documentation sites Debugging Common Issues Problem Likely Cause Bar doesn’t move Selector doesn’t match your content container Bar fills too early Article element is shorter than the viewport Bar hidden behind header Header has higher z-index than 9999 Choppy animation Scroll handler is not throttled with requestAnimationFrame FAQ Does a reading progress bar affect page speed? No. The script is a few lines and uses requestAnimationFrame, which

How to Add a Reading Progress Bar to Your Blog With CSS and JavaScript Read More »

How to Add a Table of Contents to WordPress Blog Posts Without a Plugin

If you run a WordPress blog, you already know the drill: every long-form article needs a table of contents to help readers navigate, improve dwell time, and pick up those juicy sitelinks in Google search results. The lazy solution is to install a plugin. The smart solution is to build it yourself. In this tutorial, we will show you how to add a table of contents in WordPress without a plugin by parsing your H2 and H3 headings automatically with a few lines of PHP and a sprinkle of JavaScript. No bloat, no third-party scripts, no performance hit. Why Build a Table of Contents Without a Plugin? Most tutorials on the first page of Google will tell you to manually create anchor tags and lists for every post. That works, but it is tedious and error-prone. Others will push you toward heavy plugins like Easy Table of Contents. Here is why the DIY approach wins in 2026: Performance: No extra HTTP requests, no jQuery dependency, no admin bloat. Full control: You decide the markup, the styling, and the behavior. SEO friendly: Clean semantic HTML with proper anchor links helps Google generate jump-to links in the SERP. No plugin conflicts: One less thing to update or break during a core WordPress upgrade. Reusable: Once written, it works on every post automatically. How It Works: The Logic Behind Automatic TOC Generation The idea is simple. Before WordPress prints your post content on the page, we intercept it with a filter, scan it for H2 and H3 tags, inject unique IDs into those headings, and build a nested list of links. Then we prepend that list to the content. Here is a quick overview of the pieces involved: Component Role PHP function in functions.php Parses headings, injects IDs, generates the TOC list WordPress filter the_content Applies the function to every single post CSS in your theme stylesheet Styles the TOC box Vanilla JavaScript (optional) Adds smooth scroll and collapse behavior Step 1: Add the PHP Function to Your Theme Open the functions.php file of your child theme (never edit the parent theme directly). Paste the following code at the bottom: function expressjs_generate_toc($content) { if (!is_single() || !in_the_loop() || !is_main_query()) { return $content; } // Match all H2 and H3 tags preg_match_all(‘/<h([2-3])(.*?)>(.*?)<\/h[2-3]>/i’, $content, $matches, PREG_SET_ORDER); if (count($matches) < 3) { return $content; // Skip TOC if fewer than 3 headings } $toc = ‘<div class=”expressjs-toc” id=”toc”>’; $toc .= ‘<p class=”toc-title”>Table of Contents</p>’; $toc .= ‘<ul>’; $current_level = 2; foreach ($matches as $match) { $level = (int) $match[1]; $title = strip_tags($match[3]); $anchor = sanitize_title($title); // Inject the ID into the heading inside the content $new_heading = ‘<h’ . $level . ‘ id=”‘ . $anchor . ‘”‘ . $match[2] . ‘>’ . $match[3] . ‘</h’ . $level . ‘>’; $content = str_replace($match[0], $new_heading, $content); if ($level == 3 && $current_level == 2) { $toc .= ‘<ul>’; $current_level = 3; } elseif ($level == 2 && $current_level == 3) { $toc .= ‘</ul>’; $current_level = 2; } $toc .= ‘<li><a href=”#’ . $anchor . ‘”>’ . $title . ‘</a></li>’; } if ($current_level == 3) { $toc .= ‘</ul>’; } $toc .= ‘</ul></div>’; return $toc . $content; } add_filter(‘the_content’, ‘expressjs_generate_toc’); What this code does, step by step: Runs only on single posts inside the main loop. Uses a regex to grab every H2 and H3 in the post content. Skips generation if the post has fewer than 3 headings (short articles do not need a TOC). Creates a slug for each heading using sanitize_title(). Injects an id attribute into each heading so anchor links work. Builds a nested unordered list and prepends it to the content. Step 2: Style the Table of Contents with CSS Add the following CSS to your theme’s style.css or the WordPress Customizer under Additional CSS: .expressjs-toc { background: #f7f9fc; border-left: 4px solid #0d6efd; padding: 20px 25px; margin: 30px 0; border-radius: 6px; font-size: 15px; } .expressjs-toc .toc-title { font-weight: 700; font-size: 18px; margin: 0 0 12px 0; } .expressjs-toc ul { list-style: none; padding-left: 15px; margin: 0; } .expressjs-toc ul ul { padding-left: 20px; margin-top: 6px; } .expressjs-toc li { margin: 6px 0; } .expressjs-toc a { text-decoration: none; color: #0d6efd; } .expressjs-toc a:hover { text-decoration: underline; } Feel free to adjust the colors to match your brand. This minimal style keeps the TOC readable without shouting for attention. Step 3: Add Smooth Scroll with Vanilla JavaScript Modern browsers support smooth scrolling natively via CSS, but a tiny script gives you more control (like offsetting for a sticky header). Add this to your theme’s JS file or drop it inline before the closing body tag: document.addEventListener(‘DOMContentLoaded’, function() { const links = document.querySelectorAll(‘.expressjs-toc a’); links.forEach(link => { link.addEventListener(‘click’, function(e) { e.preventDefault(); const target = document.querySelector(this.getAttribute(‘href’)); if (target) { const offset = 80; // adjust for sticky header height const top = target.getBoundingClientRect().top + window.pageYOffset – offset; window.scrollTo({ top: top, behavior: ‘smooth’ }); history.pushState(null, null, this.getAttribute(‘href’)); } }); }); }); That is it. Publish a post with a few H2 and H3 tags and reload it. You should see a clean, automatic table of contents at the top. Optional Improvements Make the TOC Collapsible If your posts are long, users may want to hide the TOC. Wrap the list in a details element for a native accordion with zero JavaScript: <details open> <summary>Table of Contents</summary> <ul>…</ul> </details> Add a Floating Sidebar TOC For desktop readers, a sticky TOC on the left side is a huge UX win. Use CSS position: sticky with a top offset and hide it on mobile with a media query. Support H4 Headings Simply update the regex pattern from [2-3] to [2-4] and add another nesting level in the loop. Common Pitfalls to Avoid Do not use it on pages with existing anchor plugins. You will get duplicate IDs. Escape HTML entities inside heading text. If your titles include quotes or ampersands, use esc_html() when building the anchor. Test with caching plugins. Clear your cache after editing functions.php or the TOC

How to Add a Table of Contents to WordPress Blog Posts Without a Plugin Read More »

About Us

Express Jam Studio was founded in 2004 by John Smith. John had previously worked for a courier company, but he saw an opportunity to start his own business in the web design and development industry.

Contact Info

Copyright © 2022 Express Jam Studio. All Rights Reserved.