Navigation & Accessibility

Free Back to Top Button Widget

A floating button that smooth-scrolls visitors back to the top of the page.

A lightweight floating button that fades in after visitors scroll past a set distance and smooth-scrolls them back to the top of the page with one click. It is ideal for long pages — blogs, docs, product listings, and landing pages — where scrolling back up is tedious. Install it with a single script tag anywhere on your page; the button pins itself to a corner of the screen.

Live demo

Embed this widget

Paste either snippet into your page — a Custom HTML block on WordPress, Shopify, or Squarespace works too. Both snippets update live as you customize, so what you copy is exactly what you configured.

Hosted (recommended)html
<script src="https://hayneslab.dev/widgets/back-to-top.js" data-hl-offset="120" data-hl-bg="#f97316" async></script>
<!-- Free Back to Top Button widget by haynes lab — https://hayneslab.dev/tools/widgets/back-to-top -->
Inline (self-contained)html
<!-- Free Back to Top Button widget by haynes lab — https://hayneslab.dev/tools/widgets/back-to-top -->
<script data-hl-offset="120" data-hl-bg="#f97316">
/*!
 * haynes lab — Back to Top Button widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/back-to-top
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-back-to-top';
  var STYLE_ID = 'hlw-back-to-top-styles';

  function findScript() {
    return document.currentScript ||
      document.querySelector('script[src*="widgets/back-to-top.js"]');
  }

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: fixed; bottom: 20px; right: 20px; z-index: 99999;' +
      ' display: flex; flex-direction: column; align-items: center; gap: 6px;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 16px; line-height: 1.5; }' +
      '.' + PREFIX + '--left { right: auto; left: 20px; }' +
      '.' + PREFIX + '--in-stage { position: absolute; z-index: 10; }' +
      '.' + PREFIX + '__button { width: 44px; height: 44px; padding: 0; border: 0; border-radius: 50%;' +
      ' background: #1f2937; color: #ffffff; cursor: pointer;' +
      ' display: flex; align-items: center; justify-content: center;' +
      ' box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);' +
      ' opacity: 0; visibility: hidden; transform: translateY(8px);' +
      ' transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s; }' +
      '.' + PREFIX + '__button--visible { opacity: 1; visibility: visible; transform: translateY(0); }' +
      '.' + PREFIX + '__button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.22); }' +
      '.' + PREFIX + '__button:focus-visible { outline: 2px solid #1f2937; outline-offset: 3px; }' +
      '.' + PREFIX + '__button svg { display: block; }' +
      '.' + PREFIX + '__credit { font-size: 11px; display: inline-flex; align-items: center; gap: 4px;' +
      ' color: #9ca3af; text-decoration: none;' +
      ' opacity: 0; visibility: hidden; transition: opacity 0.2s ease, visibility 0.2s; }' +
      '.' + PREFIX + '__button--visible ~ .' + PREFIX + '__credit { opacity: 1; visibility: visible; }' +
      '.' + PREFIX + '__credit svg { display: block; }' +
      '.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }' +
      '@media (prefers-reduced-motion: reduce) {' +
      ' .' + PREFIX + '__button, .' + PREFIX + '__credit { transition: none; } }' +
      '@media (max-width: 560px) {' +
      ' .' + PREFIX + ' { bottom: 14px; right: 14px; }' +
      ' .' + PREFIX + '--left { right: auto; left: 14px; } }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  function el(tag, className, text) {
    var node = document.createElement(tag);
    if (className) node.className = className;
    if (text != null) node.textContent = text;
    return node;
  }

  function arrowSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('width', '20');
    svg.setAttribute('height', '20');
    svg.setAttribute('fill', 'none');
    svg.setAttribute('stroke', 'currentColor');
    svg.setAttribute('stroke-width', '2.5');
    svg.setAttribute('stroke-linecap', 'round');
    svg.setAttribute('stroke-linejoin', 'round');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    var path = document.createElementNS(svgNS, 'path');
    path.setAttribute('d', 'M12 19V5M5 12l7-7 7 7');
    svg.appendChild(path);
    return svg;
  }

  function logoSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 32 32');
    svg.setAttribute('width', '13');
    svg.setAttribute('height', '13');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    function circle(cx, cy, r, attrs) {
      var c = document.createElementNS(svgNS, 'circle');
      c.setAttribute('cx', cx);
      c.setAttribute('cy', cy);
      c.setAttribute('r', r);
      for (var key in attrs) c.setAttribute(key, attrs[key]);
      svg.appendChild(c);
    }
    circle(16, 16, 16, { fill: '#0f0a0a' });
    circle(16, 16, 12, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.3 });
    circle(16, 16, 9, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.5 });
    circle(16, 16, 6, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1.2, opacity: 0.9 });
    circle(16, 16, 3, { fill: '#06b6d4' });
    circle(24.5, 7.5, 2, { fill: '#f97316' });
    circle(7, 16, 1.8, { fill: '#f97316' });
    circle(20, 20, 1.5, { fill: '#f97316' });
    return svg;
  }

  function build(script) {
    var offset = parseInt(script.getAttribute('data-hl-offset'), 10);
    if (isNaN(offset) || offset < 0) offset = 300;
    var bg = script.getAttribute('data-hl-bg') || '#1f2937';
    var left = script.getAttribute('data-hl-position') === 'left';
    var stage = script.closest('.widget-demo__stage');

    injectStyles();

    var root = el('div', PREFIX + (left ? ' ' + PREFIX + '--left' : ''));

    var button = el('button', PREFIX + '__button');
    button.type = 'button';
    button.setAttribute('aria-label', 'Back to top');
    button.style.background = bg;
    button.appendChild(arrowSvg());
    root.appendChild(button);

    var credit = el('a', PREFIX + '__credit');
    credit.appendChild(logoSvg());
    credit.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.href = 'https://hayneslab.dev/tools/widgets/back-to-top';
    credit.target = '_blank';
    credit.rel = 'noopener';
    root.appendChild(credit);

    // In the landing-page demo the button is scoped to the stage: absolute
    // positioning, stage scroll drives visibility, click scrolls the stage.
    var scrollEl = stage || window;

    function scrollTop() {
      return stage ? stage.scrollTop :
        (window.pageYOffset || document.documentElement.scrollTop || 0);
    }

    function update() {
      var visible = scrollTop() > offset;
      button.classList.toggle(PREFIX + '__button--visible', visible);
      button.setAttribute('aria-hidden', visible ? 'false' : 'true');
      button.tabIndex = visible ? 0 : -1;
    }

    var reduced = window.matchMedia &&
      window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    button.addEventListener('click', function () {
      var opts = { top: 0, behavior: reduced ? 'auto' : 'smooth' };
      try {
        scrollEl.scrollTo(opts);
      } catch (e) {
        scrollEl.scrollTop = 0; // older browsers
      }
    });

    scrollEl.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update);

    if (stage) {
      root.classList.add(PREFIX + '--in-stage');
      stage.appendChild(root);
    } else {
      document.body.appendChild(root);
    }
    update();
  }

  function init() {
    var script = findScript();
    if (!script) {
      console.warn('[haynes lab back-to-top] Could not locate its own script tag. Nothing rendered.');
      return;
    }
    build(script);
  }

  if (document.body) {
    init();
  } else {
    document.addEventListener('DOMContentLoaded', init);
  }
})();

</script>

Configuration

Every option is an attribute on the script tag — the same attributes the customizer above exposes. All attributes are optional unless marked required — defaults apply when omitted.

AttributeDefaultDescription
data-hl-offset"300"How many pixels the visitor must scroll down before the button appears.
data-hl-bg"#1f2937"Background color of the circular button; the arrow icon stays white.
data-hl-position"right"Which bottom corner the button sits in — "right" or "left".

Frequently asked questions

How do I install the Back to Top Button?

Paste the script snippet anywhere on your page — the button renders automatically in the bottom corner of the screen, appears once the visitor scrolls past the offset, and smooth-scrolls back to the top on click.

Can I change when it appears and where it sits?

Yes — set data-hl-offset to the scroll distance in pixels (default 300), data-hl-position to "left" or "right" for the corner, and data-hl-bg for the button color.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it is a single vanilla JS file with no dependencies, so it works on WordPress, Shopify, Squarespace, or any site that can embed a script tag.

More free widgets

Need a custom widget?

We build custom widgets, integrations, and whole products. Tell us what your site needs and we'll scope it.

Get in Touch