Forms & Lead Capture

Free RSVP Form Widget

Collect event RSVPs with attendance, guest count, and contact details.

A clean RSVP form for weddings, meetups, webinars, and company events — guests mark attending or not, pick a headcount, and leave their name and email. It shows your event name, date, and location up top, and POSTs responses to any endpoint you choose (Formspree, Basin, your own API). Install is one script tag wherever you want the form to appear.

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/rsvp-form.js" data-hl-action="https://formspree.io/f/your-form-id" data-hl-event="Summer Launch Party" data-hl-date="Saturday, June 14 · 6:00 PM" data-hl-location="The Terrace, Charlotte NC" async></script>
<!-- Free RSVP Form widget by haynes lab — https://hayneslab.dev/tools/widgets/rsvp-form -->
Inline (self-contained)html
<!-- Free RSVP Form widget by haynes lab — https://hayneslab.dev/tools/widgets/rsvp-form -->
<script data-hl-action="https://formspree.io/f/your-form-id" data-hl-event="Summer Launch Party" data-hl-date="Saturday, June 14 · 6:00 PM" data-hl-location="The Terrace, Charlotte NC">
/*!
 * haynes lab — RSVP Form widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/rsvp-form
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-rsvp-form';
  var STYLE_ID = 'hlw-rsvp-form-styles';

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

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; max-width: 460px; width: 100%; margin: 0 auto; padding: 28px 28px 34px;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 16px; line-height: 1.5; color: #1f2937; background: #ffffff;' +
      ' border: 1px solid #e5e7eb; border-radius: 12px; }' +
      '.' + PREFIX + '__title { display: block; font-size: 20px; font-weight: 700; color: #111827; margin: 0 0 4px; }' +
      '.' + PREFIX + '__meta { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 0 0 18px;' +
      ' font-size: 14px; color: #6b7280; }' +
      '.' + PREFIX + '__meta span { display: inline-flex; align-items: center; gap: 5px; }' +
      '.' + PREFIX + '__field { display: block; margin-bottom: 14px; }' +
      '.' + PREFIX + '__label { display: block; font-size: 13px; font-weight: 600; color: #374151; margin-bottom: 5px; }' +
      '.' + PREFIX + '__input { display: block; width: 100%; font: inherit; font-size: 15px; padding: 10px 12px;' +
      ' border: 1px solid #d1d5db; border-radius: 8px; background: #ffffff; color: #111827; }' +
      '.' + PREFIX + '__input:focus { outline: 2px solid var(--hlw-accent); outline-offset: 1px; border-color: var(--hlw-accent); }' +
      '.' + PREFIX + '__radios { display: flex; gap: 10px; margin-bottom: 14px; }' +
      '.' + PREFIX + '__radio { flex: 1 1 0; display: flex; align-items: center; justify-content: center; gap: 8px;' +
      ' font-size: 14px; font-weight: 600; color: #374151; padding: 10px 12px; cursor: pointer;' +
      ' border: 1px solid #d1d5db; border-radius: 8px; background: #ffffff; }' +
      '.' + PREFIX + '__radio input { accent-color: var(--hlw-accent); margin: 0; }' +
      '.' + PREFIX + '__radio:has(input:checked) { border-color: var(--hlw-accent); color: #111827;' +
      ' background: color-mix(in srgb, var(--hlw-accent) 7%, #ffffff); }' +
      '.' + PREFIX + '__btn { display: block; width: 100%; font: inherit; font-size: 15px; font-weight: 600;' +
      ' padding: 11px 18px; margin-top: 4px; border: 0; border-radius: 8px; background: var(--hlw-accent);' +
      ' color: #ffffff; cursor: pointer; }' +
      '.' + PREFIX + '__btn:hover { filter: brightness(1.08); }' +
      '.' + PREFIX + '__btn:focus-visible { outline: 2px solid var(--hlw-accent); outline-offset: 2px; }' +
      '.' + PREFIX + '__btn:disabled { opacity: 0.6; cursor: default; }' +
      '.' + PREFIX + '__msg { display: none; font-size: 14px; margin: 12px 0 0; }' +
      '.' + PREFIX + '__msg[data-show] { display: block; }' +
      '.' + PREFIX + '__msg[data-kind="error"] { color: #b91c1c; }' +
      '.' + PREFIX + '__success { padding: 24px 4px; text-align: center; }' +
      '.' + PREFIX + '__success-icon { display: inline-flex; align-items: center; justify-content: center;' +
      ' width: 44px; height: 44px; margin-bottom: 12px; border-radius: 50%; background: var(--hlw-accent);' +
      ' color: #ffffff; font-size: 22px; line-height: 1; }' +
      '.' + PREFIX + '__success-text { font-size: 15px; color: #374151; margin: 0; }' +
      '.' + PREFIX + '__credit { position: absolute; right: 10px; bottom: 6px; font-size: 11px;' +
      ' 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; }' +
      '@media (max-width: 480px) { .' + PREFIX + ' { padding: 22px 18px 32px; } }';
    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 action = script.getAttribute('data-hl-action');
    if (!action) {
      console.warn('[haynes lab rsvp-form] data-hl-action is required (URL the form POSTs to). Nothing rendered.');
      return;
    }
    var eventName = script.getAttribute('data-hl-event') || 'RSVP';
    var eventDate = script.getAttribute('data-hl-date');
    var eventLocation = script.getAttribute('data-hl-location');
    var accent = script.getAttribute('data-hl-accent') || '#f97316';
    var successMessage = script.getAttribute('data-hl-success-message') ||
      'Thanks — your RSVP has been received!';

    injectStyles();

    var root = el('div', PREFIX);
    root.style.setProperty('--hlw-accent', accent);

    root.appendChild(el('span', PREFIX + '__title', eventName));
    if (eventDate || eventLocation) {
      var meta = el('p', PREFIX + '__meta');
      if (eventDate) meta.appendChild(el('span', null, '📅 ' + eventDate));
      if (eventLocation) meta.appendChild(el('span', null, '📍 ' + eventLocation));
      root.appendChild(meta);
    }

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

    function field(labelText, input) {
      var wrap = el('label', PREFIX + '__field');
      wrap.appendChild(el('span', PREFIX + '__label', labelText));
      wrap.appendChild(input);
      return wrap;
    }

    var nameInput = el('input', PREFIX + '__input');
    nameInput.type = 'text';
    nameInput.name = 'name';
    nameInput.autocomplete = 'name';
    nameInput.placeholder = 'Your name';

    var emailInput = el('input', PREFIX + '__input');
    emailInput.type = 'email';
    emailInput.name = 'email';
    emailInput.autocomplete = 'email';
    emailInput.placeholder = '[email protected]';

    var yesRadio = document.createElement('input');
    yesRadio.type = 'radio';
    yesRadio.name = 'attending';
    yesRadio.value = 'yes';
    yesRadio.checked = true;
    var yesLabel = el('label', PREFIX + '__radio');
    yesLabel.appendChild(yesRadio);
    yesLabel.appendChild(document.createTextNode('Attending'));

    var noRadio = document.createElement('input');
    noRadio.type = 'radio';
    noRadio.name = 'attending';
    noRadio.value = 'no';
    var noLabel = el('label', PREFIX + '__radio');
    noLabel.appendChild(noRadio);
    noLabel.appendChild(document.createTextNode('Can’t make it'));

    var radios = el('div', PREFIX + '__radios');
    radios.setAttribute('role', 'radiogroup');
    radios.setAttribute('aria-label', 'Will you be attending?');
    radios.appendChild(yesLabel);
    radios.appendChild(noLabel);

    var guestsSelect = el('select', PREFIX + '__input');
    guestsSelect.name = 'guests';
    ['1 (just me)', '2', '3', '4', '5+'].forEach(function (label) {
      var opt = document.createElement('option');
      opt.value = label === '5+' ? '5+' : label.charAt(0);
      opt.textContent = label;
      guestsSelect.appendChild(opt);
    });
    var guestsField = field('Number of guests (including you)', guestsSelect);

    form.addEventListener('change', function (e) {
      if (e.target.name === 'attending') {
        guestsField.style.display = noRadio.checked ? 'none' : '';
      }
    });

    form.appendChild(field('Name', nameInput));
    form.appendChild(field('Email', emailInput));
    form.appendChild(radios);
    form.appendChild(guestsField);

    var btn = el('button', PREFIX + '__btn', 'Send RSVP');
    btn.type = 'submit';
    form.appendChild(btn);

    var msg = el('p', PREFIX + '__msg');
    msg.setAttribute('role', 'status');
    msg.setAttribute('aria-live', 'polite');

    function showError(text, input) {
      msg.textContent = text;
      msg.setAttribute('data-kind', 'error');
      msg.setAttribute('data-show', '');
      if (input) input.focus();
    }

    form.addEventListener('submit', function (e) {
      e.preventDefault();
      var name = nameInput.value.trim();
      var email = emailInput.value.trim();
      if (!name) return showError('Please enter your name.', nameInput);
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
        return showError('Please enter a valid email address.', emailInput);
      }

      btn.disabled = true;
      var body = new FormData();
      body.append('name', name);
      body.append('email', email);
      body.append('attending', yesRadio.checked ? 'yes' : 'no');
      if (yesRadio.checked) body.append('guests', guestsSelect.value);
      if (script.getAttribute('data-hl-event')) {
        body.append('event', script.getAttribute('data-hl-event'));
      }
      fetch(action, { method: 'POST', body: body, headers: { Accept: 'application/json' } })
        .then(function (res) {
          if (res.ok) {
            form.style.display = 'none';
            msg.style.display = 'none';
            var success = el('div', PREFIX + '__success');
            success.setAttribute('role', 'status');
            success.appendChild(el('span', PREFIX + '__success-icon', '✓'));
            success.appendChild(el('p', PREFIX + '__success-text', successMessage));
            root.appendChild(success);
          } else {
            btn.disabled = false;
            showError('Something went wrong — please try again.');
          }
        })
        .catch(function () {
          btn.disabled = false;
          showError('Something went wrong — please try again.');
        });
    });

    root.appendChild(form);
    root.appendChild(msg);

    var credit = el('a', PREFIX + '__credit');
    credit.appendChild(logoSvg());
    credit.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.href = 'https://hayneslab.dev/tools/widgets/rsvp-form';
    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);
    }
  }

  function init() {
    var script = findScript();
    if (!script) {
      console.warn('[haynes lab rsvp-form] 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-action(required)URL the form POSTs to (Formspree, Basin, or your own endpoint). The widget does not render without it.
data-hl-event"RSVP"Event name shown as the form heading; also sent with each submission as the "event" field.
data-hl-date(none)Date text displayed under the heading (free-form, e.g. "Sat, June 14 · 6 PM").
data-hl-location(none)Location text displayed under the heading next to the date.
data-hl-accent"#f97316"Any CSS color for the submit button, selected option, focus outlines, and success icon.
data-hl-success-message"Thanks — your RSVP has been received!"Text shown in place of the form after a successful submission.

Frequently asked questions

How do I install the RSVP form?

Paste the embed snippet where you want the form to appear — it renders inline at that spot. Set data-hl-action to the URL that should receive responses (a Formspree/Basin form endpoint or your own API) and you are done.

How do I show my event details and change the look?

Everything is controlled through data-hl-* attributes on the script tag: data-hl-event, data-hl-date, and data-hl-location for the header, data-hl-accent for the highlight color, and data-hl-success-message for what guests see after submitting. No CSS editing required.

Does it work on WordPress, Shopify, or Squarespace?

Yes. The widget is a single dependency-free vanilla JS file, so it runs anywhere you can paste a script tag — WordPress (Custom HTML block), Shopify (theme.liquid), Squarespace (code injection), Webflow, or a plain HTML site.

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