Forms & Lead Capture

Free Email Signup Form Widget

A newsletter signup that posts straight to your list.

A compact email capture form with built-in validation and success/error states. Point it at any form endpoint — your newsletter tool, a form backend, or your own API — and new subscribers flow in. No tracking scripts, no third-party branding.

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/email-signup.js" data-hl-action="https://formspree.io/f/your-form-id" data-hl-title="Get one practical growth tip every week" data-hl-placeholder="[email protected]" data-hl-success-message="You're in — check your inbox to confirm." async></script>
<!-- Free Email Signup Form widget by haynes lab — https://hayneslab.dev/tools/widgets/email-signup -->
Inline (self-contained)html
<!-- Free Email Signup Form widget by haynes lab — https://hayneslab.dev/tools/widgets/email-signup -->
<script data-hl-action="https://formspree.io/f/your-form-id" data-hl-title="Get one practical growth tip every week" data-hl-placeholder="[email protected]" data-hl-success-message="You're in — check your inbox to confirm.">
/*!
 * haynes lab — Email Signup Form widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/email-signup
 */
(function () {
  'use strict';

  var script = document.currentScript ||
    document.querySelector('script[src*="widgets/email-signup.js"]');

  function getConfig() {
    var d = script && script.dataset ? script.dataset : {};
    var extra = {};
    if (script && script.getAttribute('data-hl-config')) {
      try {
        var parsed = JSON.parse(script.getAttribute('data-hl-config'));
        if (parsed && typeof parsed === 'object') extra = parsed;
      } catch (e) {
        console.warn('[hlw-email-signup] Ignoring malformed JSON in data-hl-config.');
      }
    }
    function pick(key, camel, fallback) {
      if (script && script.getAttribute(key) != null) return script.getAttribute(key);
      if (extra[camel] != null) return String(extra[camel]);
      return fallback;
    }
    return {
      action: pick('data-hl-action', 'action', ''),
      title: pick('data-hl-title', 'title', 'Join the list'),
      placeholder: pick('data-hl-placeholder', 'placeholder', '[email protected]'),
      buttonText: pick('data-hl-button-text', 'buttonText', 'Subscribe'),
      successMessage: pick('data-hl-success-message', 'successMessage', "Thanks — you're on the list!"),
      bg: pick('data-hl-bg', 'bg', '#0f0a0a'),
      accent: pick('data-hl-accent', 'accent', '#f97316')
    };
  }

  var cfg = getConfig();
  if (!cfg.action) {
    console.warn('[hlw-email-signup] data-hl-action is required (URL the form POSTs to). Widget not rendered.');
    return;
  }

  function injectStyles() {
    if (document.getElementById('hlw-email-signup-styles')) return;
    var css =
      '.hlw-email-signup, .hlw-email-signup * { box-sizing: border-box; margin: 0; padding: 0; }' +
      '.hlw-email-signup { all: initial; display: block; max-width: 460px; width: 100%;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 16px; line-height: 1.5; border-radius: 12px; padding: 24px;' +
      ' background: #0f0a0a; color: #f5f5f5; box-shadow: 0 4px 24px rgba(0,0,0,.18); }' +
      '.hlw-email-signup-title { display: block; font-size: 1.15em; font-weight: 700; line-height: 1.35; margin-bottom: 14px; }' +
      '.hlw-email-signup-row { display: flex; gap: 8px; flex-wrap: wrap; }' +
      '.hlw-email-signup-input { flex: 1 1 180px; min-width: 0; font: inherit; font-size: .95em; padding: 10px 12px;' +
      ' border: 1px solid rgba(128,128,128,.45); border-radius: 8px; background: rgba(255,255,255,.08); color: inherit; }' +
      '.hlw-email-signup-input::placeholder { color: rgba(160,160,160,.8); }' +
      '.hlw-email-signup-input:focus { outline: 2px solid rgba(128,128,128,.6); outline-offset: 1px; }' +
      '.hlw-email-signup-btn { flex: 0 0 auto; font: inherit; font-size: .95em; font-weight: 600; padding: 10px 18px;' +
      ' border: 0; border-radius: 8px; background: #f97316; color: #fff; cursor: pointer; }' +
      '.hlw-email-signup-btn:hover { filter: brightness(1.08); }' +
      '.hlw-email-signup-btn:disabled { opacity: .6; cursor: default; }' +
      '.hlw-email-signup-msg { display: none; font-size: .88em; margin-top: 12px; }' +
      '.hlw-email-signup-msg[data-show] { display: block; }' +
      '.hlw-email-signup-msg[data-kind="error"] { color: #fca5a5; }' +
      '.hlw-email-signup-credit { display: block; text-align: right; margin-top: 12px; }' +
      '.hlw-email-signup-credit a { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: rgba(160,160,160,.75); text-decoration: none; }' +
      '.hlw-email-signup-credit a svg { display: block; }' +
      '.hlw-email-signup-credit a:hover { text-decoration: underline; }';
    var style = document.createElement('style');
    style.id = 'hlw-email-signup-styles';
    style.textContent = css;
    (document.head || document.documentElement).appendChild(style);
  }

  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 buildWidget() {
    var root = document.createElement('div');
    root.className = 'hlw-email-signup';
    root.style.background = cfg.bg;
    root.style.setProperty('--hlw-accent', cfg.accent);

    var title = document.createElement('span');
    title.className = 'hlw-email-signup-title';
    title.textContent = cfg.title;

    var form = document.createElement('form');
    form.noValidate = true;

    var row = document.createElement('div');
    row.className = 'hlw-email-signup-row';

    var input = document.createElement('input');
    input.className = 'hlw-email-signup-input';
    input.type = 'email';
    input.name = 'email';
    input.placeholder = cfg.placeholder;
    input.required = true;
    input.setAttribute('aria-label', 'Email address');

    var btn = document.createElement('button');
    btn.className = 'hlw-email-signup-btn';
    btn.type = 'submit';
    btn.textContent = cfg.buttonText;
    btn.style.background = cfg.accent;
    btn.setAttribute('aria-label', cfg.buttonText);

    row.appendChild(input);
    row.appendChild(btn);
    form.appendChild(row);

    var msg = document.createElement('p');
    msg.className = 'hlw-email-signup-msg';
    msg.setAttribute('role', 'status');
    msg.setAttribute('aria-live', 'polite');

    var credit = document.createElement('span');
    credit.className = 'hlw-email-signup-credit';
    var link = document.createElement('a');
    link.href = 'https://hayneslab.dev/tools/widgets/email-signup';
    link.target = '_blank';
    link.rel = 'noopener';
    link.appendChild(logoSvg());
    link.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.appendChild(link);

    function showMsg(text, kind) {
      msg.textContent = text;
      msg.setAttribute('data-kind', kind);
      msg.setAttribute('data-show', '');
    }

    form.addEventListener('submit', function (e) {
      e.preventDefault();
      var email = input.value.trim();
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
        showMsg('Please enter a valid email address.', 'error');
        input.focus();
        return;
      }
      btn.disabled = true;
      var body = new FormData();
      body.append('email', email);
      fetch(cfg.action, { method: 'POST', body: body, headers: { Accept: 'application/json' } })
        .then(function (res) {
          if (res.ok) {
            form.style.display = 'none';
            showMsg(cfg.successMessage, 'success');
          } else {
            btn.disabled = false;
            showMsg('Something went wrong — please try again.', 'error');
          }
        })
        .catch(function () {
          btn.disabled = false;
          showMsg('Something went wrong — please try again.', 'error');
        });
    });

    root.appendChild(title);
    root.appendChild(form);
    root.appendChild(msg);
    root.appendChild(credit);
    return root;
  }

  function mount() {
    injectStyles();
    var el = buildWidget();
    if (script && script.parentNode) {
      script.insertAdjacentElement('afterend', el);
    } else {
      document.body.appendChild(el);
    }
  }

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

</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-action(required)URL the form POSTs to (the email field is sent as "email"). Use your newsletter tool's form endpoint or a form backend like Formspree.
data-hl-title"Join the list"Heading displayed above the input.
data-hl-placeholder"[email protected]"Placeholder text inside the email input.
data-hl-button-text"Subscribe"Label of the submit button.
data-hl-success-message"Thanks — you're on the list!"Shown after a successful submit (HTTP 2xx from the endpoint).
data-hl-bg"#0f0a0a"Any CSS color for the form panel background.
data-hl-accent"#f97316"Any CSS color for the submit button.

Frequently asked questions

How do I install the email signup form?

Paste the embed snippet where you want the form and set data-hl-action to the URL that should receive submissions — your newsletter provider's form endpoint or a form backend such as Formspree. Submissions POST a single "email" field.

How do I customize the copy and colors?

Use the data-hl-* attributes on the script tag: data-hl-title, data-hl-placeholder, data-hl-button-text, and data-hl-success-message control the words; data-hl-bg and data-hl-accent control the colors. No CSS editing needed.

Does it work on WordPress, Shopify, or Squarespace?

Yes. It is dependency-free vanilla JS, so any platform that accepts a Custom HTML block or a script tag works. The only requirement is a form endpoint that accepts a POST — nearly every newsletter tool provides one.

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