# RSVP form template

A free RSVP form with attendance, guest count and a note. Copy the HTML, CSS and JS, or use it in React, Next.js, Vue, Svelte or Astro.

This RSVP form asks whether each guest is coming and how many people they are bringing. Name, email and attendance are required. Extra guests and a note for dietary needs or questions are optional.

Live preview, field editor, four themes and styled versions for every framework: https://nisuform.com/templates/rsvp-form

## Fields

| Field | Name | Type | Required |
| --- | --- | --- | --- |
| Name | `name` | Short text | Yes |
| Email | `email` | Email | Yes |
| Will you attend? | `attending` | Multiple choice (Yes, I’ll be there, Sorry, can’t make it) | Yes |
| Additional guests | `guests` | Number | No |
| Anything we should know? | `note` | Long text | No |

## How to use it

1. Create a free form at https://app.nisuform.com and copy its form key.
2. Paste the code where the form should appear and replace `YOUR_FORM_KEY` with your key.
3. Submit the form once. The submission appears in your Nisuform inbox and you get an email.

## Styled HTML, CSS and JavaScript

The form with its styles and a small script that sends it without a page reload and shows the success message.

```html
<form id="nf-rsvp-form" class="nf-form" action="https://api.nisuform.com/s/YOUR_FORM_KEY" method="POST">
  <style>
#nf-rsvp-form {
  --nf-accent: #e60058;
  --nf-on-accent: #ffffff;
  --nf-text: #171717;
  --nf-muted: #525252;
  --nf-field: #f5f5f5;
  --nf-field-text: #171717;
  --nf-placeholder: #6b6b6b;
  --nf-control: #8a8a8a;
  --nf-error: #b91c1c;
  color-scheme: light;
  box-sizing: border-box;
  width: 100%;
  max-width: 480px;
  margin: 0;
  padding: 28px;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 16px;
  background: linear-gradient(180deg, #ffffff, #ffffff);
  box-shadow: 0 16px 40px -16px rgba(0, 0, 0, 0.12);
  color: var(--nf-text);
  font: inherit;
  font-size: 16px;
  line-height: 1.5;
  text-align: left;
}
#nf-rsvp-form *, #nf-rsvp-form *::before, #nf-rsvp-form *::after { box-sizing: border-box; }
#nf-rsvp-form [hidden] { display: none !important; }
#nf-rsvp-form .nf-title { margin: 0; font-size: 1.25em; font-weight: 650; line-height: 1.25; letter-spacing: -0.01em; color: var(--nf-text); }
#nf-rsvp-form .nf-desc { margin: 6px 0 0; font-size: 0.9375em; color: var(--nf-muted); }
#nf-rsvp-form .nf-fields { display: grid; gap: 18px; margin-top: 22px; }
#nf-rsvp-form .nf-head + .nf-fields { margin-top: 22px; }
#nf-rsvp-form > .nf-fields:first-child { margin-top: 0; }
#nf-rsvp-form .nf-field { margin: 0; padding: 0; border: 0; min-width: 0; }
#nf-rsvp-form .nf-label { display: block; margin: 0 0 6px; padding: 0; font-size: 0.875em; font-weight: 600; color: var(--nf-text); }
#nf-rsvp-form .nf-req { color: var(--nf-muted); font-weight: 400; }
#nf-rsvp-form .nf-help { margin: 6px 0 0; font-size: 0.8125em; color: var(--nf-muted); }
#nf-rsvp-form .nf-input { display: block; width: 100%; margin: 0; padding: 10px 12px; border: 1px solid transparent; border-radius: 10px; background: var(--nf-field); color: var(--nf-field-text); font: inherit; font-size: 1em; line-height: 1.4; outline: 2px solid transparent; outline-offset: 1px; transition: border-color 150ms, box-shadow 150ms; }
#nf-rsvp-form input.nf-input, #nf-rsvp-form select.nf-input { height: 2.75em; padding-top: 0; padding-bottom: 0; }
#nf-rsvp-form textarea.nf-input { min-height: 112px; padding-top: 10px; padding-bottom: 10px; resize: vertical; }
#nf-rsvp-form .nf-input::placeholder { color: var(--nf-placeholder); opacity: 1; }
#nf-rsvp-form .nf-input:hover { border-color: color-mix(in srgb, var(--nf-text) 14%, transparent); }
#nf-rsvp-form .nf-input:focus { border-color: var(--nf-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-rsvp-form .nf-input:user-invalid { border-color: var(--nf-error); }
#nf-rsvp-form .nf-input:user-invalid:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-error) 24%, transparent); }
#nf-rsvp-form .nf-input:-webkit-autofill { -webkit-text-fill-color: var(--nf-field-text); caret-color: var(--nf-field-text); box-shadow: inset 0 0 0 100px var(--nf-field); }
#nf-rsvp-form .nf-input:-webkit-autofill:focus { box-shadow: inset 0 0 0 100px var(--nf-field), 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-rsvp-form input[type="number"].nf-input { -moz-appearance: textfield; appearance: textfield; }
#nf-rsvp-form input[type="number"].nf-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#nf-rsvp-form input[type="number"].nf-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
#nf-rsvp-form input[type="date"].nf-input { min-width: 0; text-align: left; }
#nf-rsvp-form input[type="date"].nf-input::-webkit-date-and-time-value { text-align: left; }
#nf-rsvp-form input[type="date"].nf-input.nf-empty:not(:focus) { color: var(--nf-placeholder); }
#nf-rsvp-form input[type="date"].nf-input::-webkit-calendar-picker-indicator { margin: 0; padding: 4px; border-radius: 6px; opacity: 0.55; cursor: pointer; transition: opacity 150ms; }
#nf-rsvp-form input[type="date"].nf-input::-webkit-calendar-picker-indicator:hover { opacity: 1; }
#nf-rsvp-form select.nf-input { -webkit-appearance: none; appearance: none; padding-right: 40px; background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23525252%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M6%209l6%206l6%20-6%22%2F%3E%3C%2Fsvg%3E"); background-repeat: no-repeat; background-position: right 12px center; background-size: 16px 16px; cursor: pointer; }
#nf-rsvp-form select.nf-input:has(option[value=""]:checked) { color: var(--nf-placeholder); }
#nf-rsvp-form select.nf-input option { color: var(--nf-field-text); }
#nf-rsvp-form .nf-options { display: grid; gap: 8px; }
#nf-rsvp-form .nf-option { display: flex; align-items: center; gap: 10px; min-height: 2.75em; margin: 0; padding: 10px 12px; border: 1px solid color-mix(in srgb, var(--nf-text) 14%, transparent); border-radius: 10px; background: transparent; color: var(--nf-text); font-size: 0.9375em; line-height: 1.4; cursor: pointer; transition: border-color 150ms, background-color 150ms; }
#nf-rsvp-form .nf-option:hover { border-color: color-mix(in srgb, var(--nf-text) 28%, transparent); }
#nf-rsvp-form .nf-option:has(input:checked) { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, transparent); }
#nf-rsvp-form .nf-option span { min-width: 0; overflow-wrap: anywhere; }
#nf-rsvp-form .nf-option input { -webkit-appearance: none; appearance: none; flex: none; width: 18px; height: 18px; margin: 0; border: 1.5px solid var(--nf-control); border-radius: 50%; background: transparent; outline: 2px solid transparent; cursor: pointer; transition: border-color 150ms, box-shadow 150ms; }
#nf-rsvp-form .nf-option input:checked { border-color: var(--nf-accent); background: radial-gradient(circle, var(--nf-on-accent) 0 3px, var(--nf-accent) 3.5px); }
#nf-rsvp-form .nf-option input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-rsvp-form .nf-check { display: flex; align-items: flex-start; gap: 10px; margin: 0; font-size: 0.9375em; line-height: 1.5; color: var(--nf-text); cursor: pointer; }
#nf-rsvp-form .nf-check input { -webkit-appearance: none; appearance: none; flex: none; width: 20px; height: 20px; margin: 1px 0 0; border: 1.5px solid var(--nf-control); border-radius: 6px; background: var(--nf-field) center / 14px 14px no-repeat; outline: 2px solid transparent; cursor: pointer; transition: background-color 150ms, border-color 150ms, box-shadow 150ms; }
#nf-rsvp-form .nf-check input:checked { border-color: var(--nf-accent); background-color: var(--nf-accent); background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%223.5%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M5%2012l5%205l10%20-10%22%2F%3E%3C%2Fsvg%3E"); }
#nf-rsvp-form .nf-check input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-rsvp-form .nf-check input:user-invalid { border-color: var(--nf-error); }
#nf-rsvp-form .nf-rating { display: flex; flex-wrap: wrap; margin: 0 0 0 -4px; }
#nf-rsvp-form .nf-rating label { display: inline-flex; padding: 4px; border-radius: 8px; color: var(--nf-control); color: color-mix(in srgb, var(--nf-control) 45%, transparent); cursor: pointer; transition: color 150ms; }
#nf-rsvp-form .nf-rating svg { width: 30px; height: 30px; fill: currentColor; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; transition: transform 150ms; }
#nf-rsvp-form .nf-rating input:checked + label, #nf-rsvp-form .nf-rating label:has(~ input:checked) { color: var(--nf-accent); }
#nf-rsvp-form .nf-rating:hover input + label { color: var(--nf-control); color: color-mix(in srgb, var(--nf-control) 45%, transparent); }
#nf-rsvp-form .nf-rating:hover label:hover, #nf-rsvp-form .nf-rating:hover label:has(~ label:hover) { color: var(--nf-accent); }
#nf-rsvp-form .nf-rating label:active svg { transform: scale(0.88); }
#nf-rsvp-form .nf-rating input:focus-visible + label { outline: 2px solid var(--nf-accent); outline-offset: 0; }
#nf-rsvp-form .nf-file { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; min-height: 112px; padding: 16px; border: 1.5px dashed var(--nf-control); border-color: color-mix(in srgb, var(--nf-control) 55%, transparent); border-radius: 10px; background: var(--nf-field); color: var(--nf-muted); text-align: center; transition: border-color 150ms, background-color 150ms, box-shadow 150ms; }
#nf-rsvp-form .nf-file-input { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
#nf-rsvp-form .nf-file svg { width: 22px; height: 22px; margin-bottom: 4px; }
#nf-rsvp-form .nf-file-text { margin: 0; max-width: 100%; font-size: 0.875em; overflow-wrap: anywhere; }
#nf-rsvp-form .nf-file-cta { font-weight: 600; color: var(--nf-text); }
#nf-rsvp-form .nf-file-types { margin: 0; font-size: 0.75em; color: var(--nf-placeholder); }
#nf-rsvp-form .nf-file:hover { border-color: var(--nf-control); }
#nf-rsvp-form .nf-file:focus-within { border-color: var(--nf-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-rsvp-form .nf-file.nf-file-over { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, var(--nf-field)); }
#nf-rsvp-form .nf-file.nf-file-chosen { border-style: solid; border-color: var(--nf-accent); }
#nf-rsvp-form .nf-file.nf-file-chosen .nf-file-text { font-weight: 600; color: var(--nf-text); }
#nf-rsvp-form .nf-file:has(.nf-file-input:user-invalid) { border-color: var(--nf-error); }
#nf-rsvp-form .nf-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
#nf-rsvp-form .nf-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
#nf-rsvp-form .nf-captcha { margin-top: 18px; }
#nf-rsvp-form .nf-submit { display: inline-flex; align-items: center; justify-content: center; width: 100%; min-height: 44px; margin: 22px 0 0; padding: 10px 18px; border: 0; border-radius: 10px; background: var(--nf-accent); color: var(--nf-on-accent); font: inherit; font-size: 1em; font-weight: 600; cursor: pointer; transition: filter 150ms, transform 150ms; }
#nf-rsvp-form .nf-submit:hover { filter: brightness(0.94); }
#nf-rsvp-form .nf-submit:active { transform: scale(0.98); }
#nf-rsvp-form .nf-submit:focus-visible { outline: 2px solid var(--nf-accent); outline-offset: 3px; }
#nf-rsvp-form .nf-submit:disabled { opacity: 0.6; cursor: default; }
#nf-rsvp-form .nf-error { margin: 14px 0 0; padding: 10px 12px; border-radius: 10px; background: color-mix(in srgb, var(--nf-error) 10%, transparent); font-size: 0.875em; color: var(--nf-error); }
#nf-rsvp-form.nf-sent { display: flex; flex-direction: column; justify-content: center; min-height: min(var(--nf-height, 0px), 440px); }
#nf-rsvp-form .nf-success { display: flex; flex-direction: column; align-items: center; padding: 20px 0; text-align: center; outline: none; }
#nf-rsvp-form .nf-success-icon { display: flex; flex: none; align-items: center; justify-content: center; width: 56px; height: 56px; margin: 0 0 20px; border-radius: 50%; background: var(--nf-accent); color: var(--nf-on-accent); box-shadow: 0 0 0 8px color-mix(in srgb, var(--nf-accent) 12%, transparent); }
#nf-rsvp-form .nf-success-icon svg { display: block; width: 28px; height: 28px; margin: 0; stroke-dasharray: 1; }
#nf-rsvp-form .nf-success-title { margin: 0; max-width: 24em; font-size: 1.125em; font-weight: 650; line-height: 1.35; letter-spacing: -0.01em; color: var(--nf-text); text-wrap: balance; overflow-wrap: anywhere; }
#nf-rsvp-form .nf-success-text { margin: 0; max-width: 30em; font-size: 0.9375em; line-height: 1.5; color: var(--nf-text); text-wrap: pretty; overflow-wrap: anywhere; }
#nf-rsvp-form .nf-success-title + .nf-success-text { margin-top: 6px; color: var(--nf-muted); }
#nf-rsvp-form.nf-sent .nf-success { animation: nf-success-in 400ms cubic-bezier(0.16, 1, 0.3, 1) both; }
#nf-rsvp-form.nf-sent .nf-success-icon { animation: nf-success-pop 500ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }
#nf-rsvp-form.nf-sent .nf-success-icon path { animation: nf-success-draw 360ms 160ms cubic-bezier(0.65, 0, 0.35, 1) both; }
@keyframes nf-success-in { from { opacity: 0; transform: translateY(6px); } }
@keyframes nf-success-pop { from { transform: scale(0.6); } }
@keyframes nf-success-draw { from { stroke-dashoffset: 1; } }
@media (prefers-reduced-motion: reduce) { #nf-rsvp-form * { transition: none !important; animation: none !important; } }
  </style>
  <div class="nf-head">
    <h2 class="nf-title">You’re invited</h2>
    <p class="nf-desc">Let us know if you can make it.</p>
  </div>
  <div class="nf-fields">
    <div class="nf-field">
      <label class="nf-label" for="nf-rsvp-form-name">Name<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-rsvp-form-name" name="name" type="text" autocomplete="name" placeholder="Jane Smith" required>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-rsvp-form-email">Email<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-rsvp-form-email" name="email" type="email" autocomplete="email" placeholder="jane@example.com" required>
    </div>
    <fieldset class="nf-field nf-group">
      <legend class="nf-label">Will you attend?<span class="nf-req" aria-hidden="true"> *</span></legend>
      <div class="nf-options">
        <label class="nf-option"><input type="radio" name="attending" value="Yes, I’ll be there" required><span>Yes, I’ll be there</span></label>
        <label class="nf-option"><input type="radio" name="attending" value="Sorry, can’t make it"><span>Sorry, can’t make it</span></label>
      </div>
    </fieldset>
    <div class="nf-field">
      <label class="nf-label" for="nf-rsvp-form-guests">Additional guests</label>
      <input class="nf-input" id="nf-rsvp-form-guests" name="guests" type="number" inputmode="decimal" min="0" max="10" placeholder="0">
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-rsvp-form-note">Anything we should know?</label>
      <textarea class="nf-input" id="nf-rsvp-form-note" name="note" rows="4"></textarea>
    </div>
  </div>
  <div class="nf-hp" aria-hidden="true"><input type="text" name="_gotcha" tabindex="-1" autocomplete="off"></div>
  <button class="nf-submit" type="submit">Send RSVP</button>
  <p class="nf-error" role="alert" hidden></p>
  <div class="nf-success" role="status" tabindex="-1" hidden>
    <span class="nf-success-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="M5 12l5 5l10 -10" pathLength="1"/></svg></span>
    <p class="nf-success-title">Thanks!</p>
    <p class="nf-success-text">Your RSVP is in.</p>
  </div>
</form>
<script>
(function () {
  var form = document.getElementById("nf-rsvp-form")
  if (!form || !window.fetch) return
  var started = Date.now()
  var button = form.querySelector('.nf-submit')
  var error = form.querySelector('.nf-error')
  var success = form.querySelector('.nf-success')
  form.addEventListener('submit', function (event) {
    event.preventDefault()
    var data = new FormData(form)
    data.set('_elapsed', String(Date.now() - started))
    var label = button.textContent
    button.disabled = true
    button.textContent = 'Sending...'
    error.hidden = true
    fetch(form.action, {
      method: 'POST',
      headers: { Accept: 'application/json' },
      body: new URLSearchParams(data),
    })
      .then(function (response) {
        return response.json().catch(function () { return {} }).then(function (body) {
          if (!response.ok || !body.ok) throw new Error(body.message || 'Something went wrong. Please try again.')
        })
      })
      .then(function () {
        var redirect = form.getAttribute('data-redirect')
        if (redirect) {
          window.location.href = redirect
          return
        }
        form.style.setProperty('--nf-height', form.offsetHeight + 'px')
        Array.prototype.forEach.call(form.children, function (child) {
          if (child !== success && child.tagName !== 'STYLE') child.hidden = true
        })
        form.classList.add('nf-sent')
        success.hidden = false
        success.focus()
      })
      .catch(function (reason) {
        error.textContent = reason && reason.message ? reason.message : 'Something went wrong. Please try again.'
        error.hidden = false
      })
      .finally(function () {
        button.disabled = false
        button.textContent = label
        if (window.turnstile) window.turnstile.reset()
      })
  })
})()
</script>
```

## Unstyled HTML

Bare markup that picks up your own CSS. It posts without JavaScript and keeps the hidden `_gotcha` field that stops bots.

```html
<form action="https://api.nisuform.com/s/YOUR_FORM_KEY" method="POST">
  <label>
    Name
    <input name="name" type="text" placeholder="Jane Smith" required>
  </label>
  <label>
    Email
    <input name="email" type="email" placeholder="jane@example.com" required>
  </label>
  <fieldset>
    <legend>Will you attend?</legend>
    <label><input type="radio" name="attending" value="Yes, I’ll be there" required> Yes, I’ll be there</label>
    <label><input type="radio" name="attending" value="Sorry, can’t make it"> Sorry, can’t make it</label>
  </fieldset>
  <label>
    Additional guests
    <input name="guests" type="number" min="0" max="10" placeholder="0">
  </label>
  <label>
    Anything we should know?
    <textarea name="note" rows="5"></textarea>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Send RSVP</button>
</form>
```

## Framework components

### React

```jsx
import { useRef, useState } from 'react'

export function RsvpForm() {
  const renderedAt = useRef(Date.now())
  const [status, setStatus] = useState('idle')

  async function onSubmit(event) {
    event.preventDefault()
    setStatus('sending')
    const data = Object.fromEntries(new FormData(event.currentTarget))
    data._elapsed = String(Date.now() - renderedAt.current)
    const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify(data),
    })
    setStatus(response.ok ? 'sent' : 'error')
  }

  if (status === 'sent') return <p>Thanks! Your RSVP is in.</p>

  return (
    <form onSubmit={onSubmit}>
      <label>
        Name
        <input name="name" type="text" placeholder="Jane Smith" required />
      </label>
      <label>
        Email
        <input name="email" type="email" placeholder="jane@example.com" required />
      </label>
      <fieldset>
        <legend>Will you attend?</legend>
        <label><input type="radio" name="attending" value="Yes, I’ll be there" required /> Yes, I’ll be there</label>
        <label><input type="radio" name="attending" value="Sorry, can’t make it" /> Sorry, can’t make it</label>
      </fieldset>
      <label>
        Additional guests
        <input name="guests" type="number" min="0" max="10" placeholder="0" />
      </label>
      <label>
        Anything we should know?
        <textarea name="note" rows={5}></textarea>
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Send RSVP</button>
      {status === 'error' && <p role="alert">Something went wrong. Please try again.</p>}
    </form>
  )
}
```

### Next.js

```jsx
'use client'

import { useRef, useState } from 'react'

export function RsvpForm() {
  const renderedAt = useRef(Date.now())
  const [status, setStatus] = useState('idle')

  async function onSubmit(event) {
    event.preventDefault()
    setStatus('sending')
    const data = Object.fromEntries(new FormData(event.currentTarget))
    data._elapsed = String(Date.now() - renderedAt.current)
    const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify(data),
    })
    setStatus(response.ok ? 'sent' : 'error')
  }

  if (status === 'sent') return <p>Thanks! Your RSVP is in.</p>

  return (
    <form onSubmit={onSubmit}>
      <label>
        Name
        <input name="name" type="text" placeholder="Jane Smith" required />
      </label>
      <label>
        Email
        <input name="email" type="email" placeholder="jane@example.com" required />
      </label>
      <fieldset>
        <legend>Will you attend?</legend>
        <label><input type="radio" name="attending" value="Yes, I’ll be there" required /> Yes, I’ll be there</label>
        <label><input type="radio" name="attending" value="Sorry, can’t make it" /> Sorry, can’t make it</label>
      </fieldset>
      <label>
        Additional guests
        <input name="guests" type="number" min="0" max="10" placeholder="0" />
      </label>
      <label>
        Anything we should know?
        <textarea name="note" rows={5}></textarea>
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Send RSVP</button>
      {status === 'error' && <p role="alert">Something went wrong. Please try again.</p>}
    </form>
  )
}
```

### Vue

```vue
<script setup>
import { ref } from 'vue'

const shownAt = Date.now()
const status = ref('idle')

async function onSubmit(event) {
  status.value = 'sending'
  const data = new FormData(event.target)
  data.set('_elapsed', String(Date.now() - shownAt))
  const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
    method: 'POST',
    headers: { Accept: 'application/json' },
    body: data,
  })
  status.value = response.ok ? 'sent' : 'error'
}
</script>

<template>
  <p v-if="status === 'sent'">Thanks! Your RSVP is in.</p>
  <form v-else @submit.prevent="onSubmit">
    <label>
      Name
      <input name="name" type="text" placeholder="Jane Smith" required>
    </label>
    <label>
      Email
      <input name="email" type="email" placeholder="jane@example.com" required>
    </label>
    <fieldset>
      <legend>Will you attend?</legend>
      <label><input type="radio" name="attending" value="Yes, I’ll be there" required> Yes, I’ll be there</label>
      <label><input type="radio" name="attending" value="Sorry, can’t make it"> Sorry, can’t make it</label>
    </fieldset>
    <label>
      Additional guests
      <input name="guests" type="number" min="0" max="10" placeholder="0">
    </label>
    <label>
      Anything we should know?
      <textarea name="note" rows="5"></textarea>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" :disabled="status === 'sending'">Send RSVP</button>
    <p v-if="status === 'error'" role="alert">Something went wrong. Please try again.</p>
  </form>
</template>
```

### Svelte

```svelte
<script>
  const shownAt = Date.now()
  let status = $state('idle')

  async function onsubmit(event) {
    event.preventDefault()
    status = 'sending'
    const data = new FormData(event.currentTarget)
    data.set('_elapsed', String(Date.now() - shownAt))
    const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
      method: 'POST',
      headers: { Accept: 'application/json' },
      body: data,
    })
    status = response.ok ? 'sent' : 'error'
  }
</script>

{#if status === 'sent'}
  <p>Thanks! Your RSVP is in.</p>
{:else}
  <form {onsubmit}>
    <label>
      Name
      <input name="name" type="text" placeholder="Jane Smith" required>
    </label>
    <label>
      Email
      <input name="email" type="email" placeholder="jane@example.com" required>
    </label>
    <fieldset>
      <legend>Will you attend?</legend>
      <label><input type="radio" name="attending" value="Yes, I’ll be there" required> Yes, I’ll be there</label>
      <label><input type="radio" name="attending" value="Sorry, can’t make it"> Sorry, can’t make it</label>
    </fieldset>
    <label>
      Additional guests
      <input name="guests" type="number" min="0" max="10" placeholder="0">
    </label>
    <label>
      Anything we should know?
      <textarea name="note" rows="5"></textarea>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" disabled={status === 'sending'}>Send RSVP</button>
    {#if status === 'error'}
      <p role="alert">Something went wrong. Please try again.</p>
    {/if}
  </form>
{/if}
```

### Astro

```astro
---
const endpoint = "https://api.nisuform.com/s/YOUR_FORM_KEY"
---

<form action={endpoint} method="POST">
  <label>
    Name
    <input name="name" type="text" placeholder="Jane Smith" required>
  </label>
  <label>
    Email
    <input name="email" type="email" placeholder="jane@example.com" required>
  </label>
  <fieldset>
    <legend>Will you attend?</legend>
    <label><input type="radio" name="attending" value="Yes, I’ll be there" required> Yes, I’ll be there</label>
    <label><input type="radio" name="attending" value="Sorry, can’t make it"> Sorry, can’t make it</label>
  </fieldset>
  <label>
    Additional guests
    <input name="guests" type="number" min="0" max="10" placeholder="0">
  </label>
  <label>
    Anything we should know?
    <textarea name="note" rows="5"></textarea>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Send RSVP</button>
</form>
```

## When to use it

- RSVPs on a wedding website
- Birthday, launch and holiday party invitations
- Community meetups and free events
- Company offsites and team dinners

## Customize it

### Close RSVPs on a date

Set a close date in the form settings and the form stops accepting replies at that moment, with a message that RSVPs are closed.

### Count the guests

Export the replies as CSV and add up the `guests` column in Excel or Google Sheets. On Pro, Analytics shows the total of number fields for forms built in the dashboard.

### Ask about meals

Add a `select` with your menu options, named something like `meal`. It is saved with the rest of the reply.

## Frequently asked questions

### How do I add an RSVP form to my wedding website?

Copy the code above into your wedding website, or paste it into an HTML or embed block if you use a site builder. Create a free Nisuform form, put its key in the code, and every RSVP is saved and emailed to you.

### Can guests change their RSVP?

Each reply is stored as its own submission. If someone sends the form twice, both are in your inbox with the time they were sent, so you can go with the latest answer.

### Can I limit the number of RSVPs?

Yes. Set a maximum number of submissions and the form closes when it is full.
