Business & Operations

Free Service Status Badge Widget

A live-status pill with a green/amber/red dot and optional link to your status page.

Service Status Badge renders a compact pill showing whether your service is operational, degraded, or down — with a pulsing colored dot and a short label. It's built for SaaS products, agencies, and hosting or IT providers who want to surface uptime right on their marketing site or footer. Install it with one script tag and set the state with a single data-hl-status attribute you update yourself.

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/service-status-badge.js" async></script>
<!-- Free Service Status Badge widget by haynes lab — https://hayneslab.dev/tools/widgets/service-status-badge -->
Inline (self-contained)html
<!-- Free Service Status Badge widget by haynes lab — https://hayneslab.dev/tools/widgets/service-status-badge -->
<script>
/*!
 * haynes lab — Service Status Badge widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/service-status-badge
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-service-status-badge';
  var STYLE_ID = 'hlw-service-status-badge-styles';

  var STATUSES = {
    operational: { color: '#16a34a', label: 'All systems operational' },
    degraded: { color: '#d97706', label: 'Partial service degradation' },
    down: { color: '#dc2626', label: 'Service outage' }
  };

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

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; display: inline-flex; align-items: center; gap: 8px;' +
      ' padding: 7px 14px; font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 13px; font-weight: 500; line-height: 1.4; color: #1f2937; background: #ffffff;' +
      ' border: 1px solid #e5e7eb; border-radius: 999px; text-decoration: none; }' +
      'a.' + PREFIX + ':hover { border-color: #d1d5db; background: #f9fafb; }' +
      'a.' + PREFIX + ':focus-visible { outline: 2px solid var(--hlw-dot); outline-offset: 2px; }' +
      '.' + PREFIX + '__dot { position: relative; flex: 0 0 auto; width: 9px; height: 9px;' +
      ' border-radius: 50%; background: var(--hlw-dot); }' +
      '.' + PREFIX + '__dot::after { content: ""; position: absolute; inset: -3px; border-radius: 50%;' +
      ' background: var(--hlw-dot); opacity: 0.25; animation: ' + PREFIX + '-pulse 2s ease-out infinite; }' +
      '@keyframes ' + PREFIX + '-pulse { 0% { transform: scale(0.6); opacity: 0.35; }' +
      ' 70% { transform: scale(1.5); opacity: 0; } 100% { transform: scale(1.5); opacity: 0; } }' +
      '@media (prefers-reduced-motion: reduce) { .' + PREFIX + '__dot::after { animation: none; } }' +
      '.' + PREFIX + '__label { white-space: nowrap; }' +
      '.' + PREFIX + '__arrow { font-size: 11px; color: #9ca3af; }' +
      '.' + PREFIX + '__credit { position: absolute; right: 12px; bottom: -16px; font-size: 11px; font-weight: 400;' +
      ' display: inline-flex; align-items: center; gap: 4px; color: #9ca3af; text-decoration: none; }' +
      '.' + 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 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 build(script) {
    var status = (script.getAttribute('data-hl-status') || 'operational').toLowerCase();
    if (!STATUSES[status]) {
      console.warn('[haynes lab service-status-badge] Unknown data-hl-status "' + status +
        '" (expected operational, degraded, or down). Falling back to "operational".');
      status = 'operational';
    }
    var label = script.getAttribute('data-hl-label') || STATUSES[status].label;
    var link = script.getAttribute('data-hl-link') || '';

    injectStyles();

    var root = el(link ? 'a' : 'span', PREFIX);
    root.style.setProperty('--hlw-dot', STATUSES[status].color);
    root.setAttribute('role', 'status');
    if (link) {
      root.href = link;
      root.target = '_blank';
      root.rel = 'noopener';
      root.setAttribute('aria-label', label + ' — view status page');
    }

    root.appendChild(el('span', PREFIX + '__dot')).setAttribute('aria-hidden', 'true');
    root.appendChild(el('span', PREFIX + '__label', label));
    if (link) {
      root.appendChild(el('span', PREFIX + '__arrow', '↗')).setAttribute('aria-hidden', 'true');
    }

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

    var wrap = el('span', PREFIX + '-wrap');
    wrap.style.cssText = 'position:relative;display:inline-block;';
    wrap.appendChild(root);
    wrap.appendChild(credit);

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

  function init() {
    var script = findScript();
    if (!script) {
      console.warn('[haynes lab service-status-badge] 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-status"operational"One of "operational" (green), "degraded" (amber), or "down" (red); drives the dot color and default label.
data-hl-label(none)Optional text shown next to the dot; when omitted, a sensible default per status is used.
data-hl-link(none)Optional link to your full status page; makes the whole badge clickable and opens in a new tab.

Frequently asked questions

How do I install the Service Status Badge?

Paste the script tag where you want the badge to appear — header, footer, or next to a signup button. The badge renders inline right after the script tag; set data-hl-status to operational, degraded, or down.

Can I change the label and link it to my status page?

Yes — data-hl-label overrides the default text (for example "API: all systems go"), and data-hl-link turns the badge into a link to your full status page that opens in a new tab.

Does the status update automatically?

The badge displays whatever you set in data-hl-status. Update the attribute when your status changes — many teams regenerate it from their status page or monitoring tool during deploys.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it's a single vanilla JavaScript file with no dependencies, so it works anywhere you can paste a 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