E-commerce & Retail

Free Recent Sales Popup Widget

Social-proof toast that cycles recent purchases in the corner of your store.

A small "someone just bought" notification that pops up in the bottom-left corner and cycles through a list of recent orders. Built for e-commerce stores that want lightweight social proof without a SaaS subscription. Paste one script tag with your sales list and it runs anywhere.

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/recent-sales-popup.js" data-hl-config='[{"name":"Emma","city":"Charlotte, NC","product":"Linen Throw Pillow","minutes":4},{"name":"Liam","city":"Austin, TX","product":"Ceramic Pour-Over Set","minutes":11},{"name":"Sofia","city":"Denver, CO","product":"Walnut Desk Organizer","minutes":19},{"name":"Noah","city":"Portland, OR","product":"Soy Candle Trio","minutes":27},{"name":"Ava","city":"Nashville, TN","product":"Leather Weekender Bag","minutes":38}]' data-hl-interval="6000" async></script>
<!-- Free Recent Sales Popup widget by haynes lab — https://hayneslab.dev/tools/widgets/recent-sales-popup -->
Inline (self-contained)html
<!-- Free Recent Sales Popup widget by haynes lab — https://hayneslab.dev/tools/widgets/recent-sales-popup -->
<script data-hl-config='[{"name":"Emma","city":"Charlotte, NC","product":"Linen Throw Pillow","minutes":4},{"name":"Liam","city":"Austin, TX","product":"Ceramic Pour-Over Set","minutes":11},{"name":"Sofia","city":"Denver, CO","product":"Walnut Desk Organizer","minutes":19},{"name":"Noah","city":"Portland, OR","product":"Soy Candle Trio","minutes":27},{"name":"Ava","city":"Nashville, TN","product":"Leather Weekender Bag","minutes":38}]' data-hl-interval="6000">
/*!
 * haynes lab — Recent Sales Popup widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/recent-sales-popup
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-recent-sales-popup';
  var STYLE_ID = 'hlw-recent-sales-popup-styles';

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

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: fixed; left: 16px; bottom: 16px; z-index: 99999;' +
      ' width: min(340px, calc(100vw - 32px));' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 14px; line-height: 1.45; color: #1f2937;' +
      ' opacity: 0; transform: translateY(12px); pointer-events: none;' +
      ' transition: opacity 0.35s ease, transform 0.35s ease; }' +
      '.' + PREFIX + '--stage { position: absolute; left: 12px; bottom: 12px; z-index: 10; }' +
      '.' + PREFIX + '--visible { opacity: 1; transform: translateY(0); pointer-events: auto; }' +
      '.' + PREFIX + '__card { position: relative; display: flex; align-items: center; gap: 12px;' +
      ' background: #ffffff; border: 1px solid #e5e7eb; border-radius: 12px;' +
      ' padding: 12px 36px 12px 12px; box-shadow: 0 8px 24px rgba(17, 24, 39, 0.12); }' +
      '.' + PREFIX + '__icon { flex: 0 0 40px; width: 40px; height: 40px; border-radius: 8px;' +
      ' background: #fff7ed; display: flex; align-items: center; justify-content: center;' +
      ' font-size: 20px; line-height: 1; }' +
      '.' + PREFIX + '__body { min-width: 0; }' +
      '.' + PREFIX + '__text { margin: 0; color: #111827; }' +
      '.' + PREFIX + '__text strong { font-weight: 600; }' +
      '.' + PREFIX + '__time { margin: 2px 0 0; font-size: 12px; color: #6b7280; }' +
      '.' + PREFIX + '__close { position: absolute; top: 6px; right: 6px; width: 22px; height: 22px;' +
      ' border: 0; border-radius: 6px; background: transparent; color: #9ca3af; font-size: 14px;' +
      ' line-height: 1; cursor: pointer; padding: 0; display: flex; align-items: center; justify-content: center; }' +
      '.' + PREFIX + '__close:hover { background: #f3f4f6; color: #4b5563; }' +
      '.' + PREFIX + '__close:focus-visible { outline: 2px solid #f97316; outline-offset: 1px; }' +
      '.' + PREFIX + '__credit { display: inline-flex; align-items: center; gap: 4px; margin-top: 6px;' +
      ' font-size: 11px; color: #9ca3af; text-decoration: none;' +
      ' text-shadow: 0 1px 2px rgba(255, 255, 255, 0.8); }' +
      '.' + PREFIX + '__credit svg { display: block; }' +
      '.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  function parseSales(script) {
    var raw = script.getAttribute('data-hl-config');
    if (!raw) {
      console.warn('[haynes lab recent-sales-popup] Missing required data-hl-config attribute (JSON array of {name, city, product, minutes}). Nothing rendered.');
      return null;
    }
    var items;
    try {
      items = JSON.parse(raw);
    } catch (e) {
      console.warn('[haynes lab recent-sales-popup] data-hl-config is not valid JSON. Nothing rendered.', e);
      return null;
    }
    if (!Array.isArray(items)) {
      console.warn('[haynes lab recent-sales-popup] data-hl-config must be a JSON array. Nothing rendered.');
      return null;
    }
    var valid = items.filter(function (s) {
      return s && typeof s.name === 'string' && s.name &&
        typeof s.city === 'string' && s.city &&
        typeof s.product === 'string' && s.product;
    });
    if (!valid.length) {
      console.warn('[haynes lab recent-sales-popup] data-hl-config has no valid entries (each needs "name", "city", and "product"). Nothing rendered.');
      return null;
    }
    return valid;
  }

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

  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 timeAgo(minutes) {
    var m = parseInt(minutes, 10);
    if (isNaN(m) || m < 1) return 'just now';
    if (m === 1) return '1 minute ago';
    if (m < 60) return m + ' minutes ago';
    var h = Math.floor(m / 60);
    return h === 1 ? '1 hour ago' : h + ' hours ago';
  }

  function build(script) {
    var sales = parseSales(script);
    if (!sales) return;

    var interval = parseInt(script.getAttribute('data-hl-interval'), 10);
    if (isNaN(interval) || interval < 4000) interval = 8000;
    var inStage = !!script.closest('.widget-demo__stage');

    injectStyles();

    var root = el('div', PREFIX + (inStage ? ' ' + PREFIX + '--stage' : ''));
    root.setAttribute('role', 'status');
    root.setAttribute('aria-live', 'polite');

    var card = el('div', PREFIX + '__card');
    card.appendChild(el('div', PREFIX + '__icon', '🛍️'));
    var body = el('div', PREFIX + '__body');
    card.appendChild(body);
    var close = el('button', PREFIX + '__close', '✕');
    close.type = 'button';
    close.setAttribute('aria-label', 'Dismiss recent sales notifications');
    card.appendChild(close);
    root.appendChild(card);

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

    var index = 0;
    var timer = null;
    var dismissed = false;

    function show() {
      if (dismissed) return;
      var s = sales[index % sales.length];
      index++;

      body.textContent = '';
      var text = el('p', PREFIX + '__text');
      var strong = el('strong', null, s.name + ' from ' + s.city);
      text.appendChild(strong);
      text.appendChild(document.createTextNode(' bought ' + s.product));
      body.appendChild(text);
      body.appendChild(el('p', PREFIX + '__time', timeAgo(s.minutes)));

      root.classList.add(PREFIX + '--visible');
      timer = setTimeout(hide, 5000);
    }

    function hide() {
      root.classList.remove(PREFIX + '--visible');
      if (!dismissed) timer = setTimeout(show, interval);
    }

    close.addEventListener('click', function () {
      dismissed = true;
      if (timer) clearTimeout(timer);
      root.classList.remove(PREFIX + '--visible');
    });

    if (inStage) {
      script.closest('.widget-demo__stage').appendChild(root);
    } else {
      document.body.appendChild(root);
    }
    timer = setTimeout(show, 1500);
  }

  function init() {
    var script = findScript();
    if (!script) {
      console.warn('[haynes lab recent-sales-popup] 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-config(required)JSON array of sales, each with "name", "city", "product", and optional "minutes" (how long ago the purchase happened).
data-hl-interval"8000"Milliseconds between popups after each one hides (minimum 4000).

Frequently asked questions

How do I install the recent sales popup?

Paste the script tag anywhere before the closing </body> tag with your sales list in data-hl-config. The popup appears fixed in the bottom-left corner and cycles automatically.

Can I control how often the popup appears?

Yes — set data-hl-interval to the number of milliseconds between popups (default 8000, minimum 4000). Each popup stays visible for 5 seconds before hiding.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it is a single vanilla JS file with no dependencies, so it works on any platform that lets you add a custom script tag, including WordPress, Shopify, Squarespace, and plain HTML sites.

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