Marketing & Social

Free Countdown Timer Widget

A live countdown that builds urgency for launches and deadlines.

A clean days/hours/minutes/seconds countdown for product launches, event registrations, and limited-time offers. Set one target date and the timer does the rest, including a custom message when time runs out. No libraries, no accounts.

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/countdown-timer.js" data-hl-target="2026-12-31T23:59:59-05:00" data-hl-title="Early-bird pricing ends in" data-hl-expired-message="Early-bird pricing has ended — regular rates now apply." async></script>
<!-- Free Countdown Timer widget by haynes lab — https://hayneslab.dev/tools/widgets/countdown-timer -->
Inline (self-contained)html
<!-- Free Countdown Timer widget by haynes lab — https://hayneslab.dev/tools/widgets/countdown-timer -->
<script data-hl-target="2026-12-31T23:59:59-05:00" data-hl-title="Early-bird pricing ends in" data-hl-expired-message="Early-bird pricing has ended — regular rates now apply.">
/*!
 * haynes lab — Countdown Timer widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/countdown-timer
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-countdown-timer';
  var STYLE_ID = 'hlw-countdown-timer-styles';

  var script = document.currentScript;
  if (!script) {
    script = document.querySelector('script[src*="widgets/countdown-timer.js"]');
  }
  if (!script) {
    console.warn('[hlw countdown-timer] Could not locate its own <script> tag; widget not rendered.');
    return;
  }

  function attr(name, fallback) {
    var v = script.getAttribute(name);
    return v === null || v === '' ? fallback : v;
  }

  var target = attr('data-hl-target', null);
  var title = attr('data-hl-title', 'Time left');
  var expiredMessage = attr('data-hl-expired-message', 'This offer has ended.');
  var bg = attr('data-hl-bg', '#0f0a0a');
  var textColor = attr('data-hl-text-color', '#ffffff');

  if (!target) {
    console.warn('[hlw countdown-timer] Missing required attribute data-hl-target (ISO 8601 date/time, e.g. "2026-12-31T23:59:59-05:00"). Widget not rendered.');
    return;
  }

  var targetTime = new Date(target).getTime();
  if (isNaN(targetTime)) {
    // Try a looser parse, falling back to local time interpretation.
    var retry = new Date(String(target).replace(' ', 'T')).getTime();
    if (!isNaN(retry)) {
      targetTime = retry;
      console.warn('[hlw countdown-timer] data-hl-target "' + target + '" is not strict ISO 8601; interpreted as local time.');
    } else {
      console.warn('[hlw countdown-timer] data-hl-target "' + target + '" could not be parsed as a date. Widget not rendered.');
      return;
    }
  } else if (!/[zZ]|[+-]\d{2}:?\d{2}$/.test(target)) {
    console.warn('[hlw countdown-timer] data-hl-target "' + target + '" has no timezone offset; using the visitor\'s local time.');
  }

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; margin: 0; padding: 0; }' +
      '.' + PREFIX + ' { font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.4; display: inline-block; border-radius: 12px; padding: 20px 28px; text-align: center; max-width: 100%; }' +
      '.' + PREFIX + '__title { display: block; font-size: 0.95em; font-weight: 600; letter-spacing: 0.02em; margin-bottom: 12px; opacity: 0.85; }' +
      '.' + PREFIX + '__units { display: flex; gap: 18px; justify-content: center; flex-wrap: wrap; }' +
      '.' + PREFIX + '__unit { display: flex; flex-direction: column; align-items: center; min-width: 56px; }' +
      '.' + PREFIX + '__value { font-size: 2em; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; }' +
      '.' + PREFIX + '__label { font-size: 0.7em; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.7; margin-top: 4px; }' +
      '.' + PREFIX + '__expired { font-size: 1em; font-weight: 600; }' +
      '.' + PREFIX + '__credit { display: inline-flex; align-items: center; gap: 4px; margin-top: 12px; font-size: 11px; opacity: 0.55; text-decoration: none; color: inherit; }' +
      '.' + PREFIX + '__credit svg { display: block; }' +
      '.' + PREFIX + '__credit:hover { text-decoration: underline; opacity: 0.8; }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    (document.head || document.documentElement).appendChild(style);
  }

  function el(tag, className, text) {
    var node = document.createElement(tag);
    if (className) node.className = className;
    if (text !== undefined) 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 build() {
    injectStyles();

    var root = el('div', PREFIX);
    root.setAttribute('role', 'timer');
    root.style.backgroundColor = bg;
    root.style.color = textColor;

    if (title) root.appendChild(el('span', PREFIX + '__title', title));

    var units = el('div', PREFIX + '__units');
    var parts = [
      { label: 'Days', factor: 86400000 },
      { label: 'Hours', factor: 3600000 },
      { label: 'Minutes', factor: 60000 },
      { label: 'Seconds', factor: 1000 }
    ];
    var valueNodes = [];
    parts.forEach(function (p) {
      var unit = el('div', PREFIX + '__unit');
      var value = el('span', PREFIX + '__value', '0');
      var label = el('span', PREFIX + '__label', p.label);
      unit.appendChild(value);
      unit.appendChild(label);
      units.appendChild(unit);
      valueNodes.push({ node: value, factor: p.factor });
    });
    root.appendChild(units);

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

    if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
      script.insertAdjacentElement('afterend', root);
    } else {
      document.body.appendChild(root);
    }

    var timerId = null;

    function expire() {
      if (timerId !== null) { clearInterval(timerId); timerId = null; }
      root.setAttribute('aria-live', 'polite');
      while (root.firstChild) root.removeChild(root.firstChild);
      if (title) root.appendChild(el('span', PREFIX + '__title', title));
      root.appendChild(el('span', PREFIX + '__expired', expiredMessage));
      root.appendChild(credit);
    }

    function pad(n) { return n < 10 ? '0' + n : String(n); }

    function tick() {
      var remaining = targetTime - Date.now();
      if (remaining <= 0) { expire(); return; }
      var rest = remaining;
      valueNodes.forEach(function (entry, i) {
        var v = Math.floor(rest / entry.factor);
        rest -= v * entry.factor;
        entry.node.textContent = i === 0 ? String(v) : pad(v);
      });
    }

    tick();
    if (targetTime > Date.now()) {
      timerId = setInterval(tick, 1000);
    }
  }

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

</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-target(required)ISO 8601 date/time the timer counts down to, e.g. "2026-12-31T23:59:59-05:00". Include a timezone offset for accurate results.
data-hl-title"Time left"Short heading displayed above the timer.
data-hl-expired-message"This offer has ended."Text shown once the target date passes. The timer hides itself and shows this instead.
data-hl-bg"#0f0a0a"Any CSS color for the timer panel background.
data-hl-text-color"#ffffff"Any CSS color for the digits and labels.

Frequently asked questions

How do I install the countdown timer?

Paste the embed snippet where you want the timer to appear — it renders inline at that spot. Set data-hl-target to your deadline in ISO 8601 format (for example 2026-12-31T23:59:59-05:00) and you are done.

How do I change what happens when the timer hits zero?

Set data-hl-expired-message to the text you want visitors to see after the deadline. The timer swaps itself out for that message automatically, so expired promos never show a frozen 00:00:00.

Does it work on WordPress, Shopify, or Squarespace?

Yes. It is a single dependency-free vanilla JS file — paste the script tag into a Custom HTML block (WordPress, Shopify, Squarespace, Webflow) or directly in your HTML and it just works.

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