Party & Celebration RSVP Form
A vibrant, casual party and gathering RSVP form styled with Tailwind CSS in warm berry and sunset tones. Captures attendance responses, optional plus-one guest names, dietary requirements (vegan, gluten-free, allergies), and dance song requests, piping guest list updates straight into your Google Sheet or Telegram.
Summer Rooftop Gathering & Dinner
Saturday, July 25th • 7:00 PM • Sunset Terrace
style="color:#9ece6a;">"use client";
import { useState } from style="color:#9ece6a;">"react";
import { PartyPopper, Heart, Music, UtensilsCrossed, UserPlus } from style="color:#9ece6a;">"lucide-react";
export default function PartyRsvpForm() {
const [guestName, setGuestName] = useState(style="color:#9ece6a;">"");
const [email, setEmail] = useState(style="color:#9ece6a;">"");
const [attendance, setAttendance] = useState<style="color:#9ece6a;">"attending" | style="color:#9ece6a;">"declined">(style="color:#9ece6a;">"attending");
const [hasPlusOne, setHasPlusOne] = useState(false);
const [plusOneName, setPlusOneName] = useState(style="color:#9ece6a;">"");
const [dietary, setDietary] = useState(style="color:#9ece6a;">"None");
const [songRequest, setSongRequest] = useState(style="color:#9ece6a;">"");
const [status, setStatus] = useState<style="color:#9ece6a;">"idle" | style="color:#9ece6a;">"loading" | style="color:#9ece6a;">"success" | style="color:#9ece6a;">"error">(style="color:#9ece6a;">"idle");
const [errorMessage, setErrorMessage] = useState(style="color:#9ece6a;">"");
const dietaryOptions = [style="color:#9ece6a;">"None", style="color:#9ece6a;">"Vegetarian", style="color:#9ece6a;">"Vegan", style="color:#9ece6a;">"Gluten-Free"];
async function handleSubmit(e: React.FormEvent<style="color:#f7768e;font-weight:600;">HTMLFormElement>) {
e.preventDefault();
setStatus(style="color:#9ece6a;">"loading");
setErrorMessage(style="color:#9ece6a;">"");
const form = e.currentTarget;
const formData = new FormData(form);
formData.set(style="color:#9ece6a;">"attendance", attendance);
formData.set(style="color:#9ece6a;">"hasPlusOne", hasPlusOne ? style="color:#9ece6a;">"yes" : style="color:#9ece6a;">"no");
if (hasPlusOne) formData.set(style="color:#9ece6a;">"plusOneName", plusOneName);
formData.set(style="color:#9ece6a;">"dietary", dietary);
try {
style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">// Replace YOUR_FORM_ID with your Backforms endpoint ID from app.backforms.com
const response = await fetch(style="color:#9ece6a;">"https:style="color:#565f89;font-style:italic;style="color:#9ece6a;">">//backforms.com/f/YOUR_FORM_ID", {
method: style="color:#9ece6a;">"POST",
headers: {
Accept: style="color:#9ece6a;">"application/json",
},
body: formData,
});
if (response.ok) {
setStatus(style="color:#9ece6a;">"success");
} else {
const data = await response.json().catch(() => null);
setErrorMessage(data?.error || style="color:#9ece6a;">"RSVP submission failed. Please try again.");
setStatus(style="color:#9ece6a;">"error");
}
} catch {
setErrorMessage(style="color:#9ece6a;">"Network error. Please check your internet connection.");
setStatus(style="color:#9ece6a;">"error");
}
}
return (
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"flex flex-col items-center justify-center min-h-[500px] p-4">
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"w-full max-w-md bg-gradient-to-b from-[#1b1018] to-[#0e070c] border border-pink-500/30 rounded-2xl p-6 sm:p-7 shadow-2xl text-zinc-100">
<style="color:#f7768e;font-weight:600;">h2 className=style="color:#9ece6a;">"text-xl sm:text-2xl font-extrabold text-white mb-1">
Alex's Birthday Bash & Rooftop BBQ
</style="color:#f7768e;font-weight:600;">h2>
<style="color:#f7768e;font-weight:600;">p className=style="color:#9ece6a;">"text-xs text-zinc-400 mb-5 leading-relaxed">
Saturday, July 25th • 7:00 PM • Sunset Terrace (BYOB & good vibes)
</style="color:#f7768e;font-weight:600;">p>
{status === style="color:#9ece6a;">"success" ? (
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"p-5 bg-pink-950/60 border border-emerald-500/40 text-emerald-300 rounded-xl text-center space-y-2">
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"size-10 mx-auto rounded-full bg-emerald-950/70 border border-emerald-500/40 flex items-center justify-center text-emerald-400">
<style="color:#f7768e;font-weight:600;">PartyPopper className=style="color:#9ece6a;">"size-5" />
</style="color:#f7768e;font-weight:600;">div>
<style="color:#f7768e;font-weight:600;">p className=style="color:#9ece6a;">"font-extrabold text-lg text-white">
{attendance === style="color:#9ece6a;">"attending" ? style="color:#9ece6a;">"You're on the Guest List! 🎉" : style="color:#9ece6a;">"We'll Miss You! ❤️"}
</style="color:#f7768e;font-weight:600;">p>
<style="color:#f7768e;font-weight:600;">p className=style="color:#9ece6a;">"text-xs text-zinc-300">
Thank you, {guestName}! The host has been notified of your response.
</style="color:#f7768e;font-weight:600;">p>
<style="color:#f7768e;font-weight:600;">button
type=style="color:#9ece6a;">"button"
onClick={() => setStatus(style="color:#9ece6a;">"idle")}
className=style="color:#9ece6a;">"mt-3 px-4 py-1.5 bg-pink-600 hover:bg-pink-500 text-zinc-950 font-bold rounded-lg text-xs transition cursor-pointer"
>
Update RSVP
</style="color:#f7768e;font-weight:600;">button>
</style="color:#f7768e;font-weight:600;">div>
) : (
<style="color:#f7768e;font-weight:600;">form onSubmit={handleSubmit} className=style="color:#9ece6a;">"space-y-4">
{style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">/* Invisible Honeypot Spam Trap */}
<style="color:#f7768e;font-weight:600;">input
type=style="color:#9ece6a;">"text"
name=style="color:#9ece6a;">"_gotcha"
style={{ display: style="color:#9ece6a;">"none" }}
tabIndex={-1}
autoComplete=style="color:#9ece6a;">"off"
/>
{status === style="color:#9ece6a;">"error" && (
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"p-3 bg-red-950/80 border border-red-500/50 text-red-300 text-xs rounded-lg">
{errorMessage}
</style="color:#f7768e;font-weight:600;">div>
)}
{style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">/* Attendance Toggle */}
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"grid grid-cols-2 gap-2">
<style="color:#f7768e;font-weight:600;">button
type=style="color:#9ece6a;">"button"
onClick={() => setAttendance(style="color:#9ece6a;">"attending")}
className={`py-2.5 px-3 rounded-xl text-xs font-bold transition flex items-center justify-center gap-1.5 cursor-pointer ${
attendance === style="color:#9ece6a;">"attending"
? style="color:#9ece6a;">"bg-gradient-to-r from-pink-500 to-rose-500 text-zinc-950 shadow-md"
: style="color:#9ece6a;">"bg-[#251520] text-zinc-400 border border-pink-500/20 hover:text-white"
}`}
>
<style="color:#f7768e;font-weight:600;">PartyPopper className=style="color:#9ece6a;">"size-4" />
<style="color:#f7768e;font-weight:600;">span>Count Me In!</style="color:#f7768e;font-weight:600;">span>
</style="color:#f7768e;font-weight:600;">button>
<style="color:#f7768e;font-weight:600;">button
type=style="color:#9ece6a;">"button"
onClick={() => setAttendance(style="color:#9ece6a;">"declined")}
className={`py-2.5 px-3 rounded-xl text-xs font-bold transition flex items-center justify-center gap-1.5 cursor-pointer ${
attendance === style="color:#9ece6a;">"declined"
? style="color:#9ece6a;">"bg-zinc-800 text-rose-300 border border-rose-500/50"
: style="color:#9ece6a;">"bg-[#251520] text-zinc-400 border border-pink-500/20 hover:text-white"
}`}
>
<style="color:#f7768e;font-weight:600;">Heart className=style="color:#9ece6a;">"size-4" />
<style="color:#f7768e;font-weight:600;">span>Can't Make It</style="color:#f7768e;font-weight:600;">span>
</style="color:#f7768e;font-weight:600;">button>
</style="color:#f7768e;font-weight:600;">div>
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"grid grid-cols-1 sm:grid-cols-2 gap-2.5">
<style="color:#f7768e;font-weight:600;">div>
<style="color:#f7768e;font-weight:600;">label className=style="color:#9ece6a;">"block text-xs font-medium text-zinc-300 mb-1" htmlFor=style="color:#9ece6a;">"guestName">
Your Name
</style="color:#f7768e;font-weight:600;">label>
<style="color:#f7768e;font-weight:600;">input
id=style="color:#9ece6a;">"guestName"
name=style="color:#9ece6a;">"guestName"
type=style="color:#9ece6a;">"text"
required
placeholder=style="color:#9ece6a;">"Morgan Guest"
value={guestName}
onChange={(e) => setGuestName(e.target.value)}
className=style="color:#9ece6a;">"w-full bg-[#251520] text-white border border-pink-500/25 rounded-lg px-3 py-1.5 text-xs placeholder:text-zinc-500 focus:outline-none focus:border-pink-400 transition"
/>
</style="color:#f7768e;font-weight:600;">div>
<style="color:#f7768e;font-weight:600;">div>
<style="color:#f7768e;font-weight:600;">label className=style="color:#9ece6a;">"block text-xs font-medium text-zinc-300 mb-1" htmlFor=style="color:#9ece6a;">"email">
Email or Phone
</style="color:#f7768e;font-weight:600;">label>
<style="color:#f7768e;font-weight:600;">input
id=style="color:#9ece6a;">"email"
name=style="color:#9ece6a;">"email"
type=style="color:#9ece6a;">"text"
required
placeholder=style="color:#9ece6a;">"morgan@party.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
className=style="color:#9ece6a;">"w-full bg-[#251520] text-white border border-pink-500/25 rounded-lg px-3 py-1.5 text-xs placeholder:text-zinc-500 focus:outline-none focus:border-pink-400 transition"
/>
</style="color:#f7768e;font-weight:600;">div>
</style="color:#f7768e;font-weight:600;">div>
{attendance === style="color:#9ece6a;">"attending" && (
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"p-3 bg-[#251520]/80 border border-pink-500/20 rounded-xl space-y-2">
<style="color:#f7768e;font-weight:600;">label className=style="color:#9ece6a;">"flex items-center justify-between cursor-pointer">
<style="color:#f7768e;font-weight:600;">span className=style="color:#9ece6a;">"flex items-center gap-2 text-xs font-medium text-zinc-200">
<style="color:#f7768e;font-weight:600;">UserPlus className=style="color:#9ece6a;">"size-3.5 text-pink-400" />
<style="color:#f7768e;font-weight:600;">span>Bringing a Plus-One (+1)?</style="color:#f7768e;font-weight:600;">span>
</style="color:#f7768e;font-weight:600;">span>
<style="color:#f7768e;font-weight:600;">input
type=style="color:#9ece6a;">"checkbox"
checked={hasPlusOne}
onChange={(e) => setHasPlusOne(e.target.checked)}
className=style="color:#9ece6a;">"size-4 accent-pink-500 rounded cursor-pointer"
/>
</style="color:#f7768e;font-weight:600;">label>
{hasPlusOne && (
<style="color:#f7768e;font-weight:600;">input
type=style="color:#9ece6a;">"text"
name=style="color:#9ece6a;">"plusOneName"
placeholder=style="color:#9ece6a;">"Plus-one guest full name"
value={plusOneName}
onChange={(e) => setPlusOneName(e.target.value)}
className=style="color:#9ece6a;">"w-full bg-[#1b0e17] text-white border border-pink-500/30 rounded-lg px-3 py-1.5 text-xs placeholder:text-zinc-500 focus:outline-none focus:border-pink-400 transition"
/>
)}
</style="color:#f7768e;font-weight:600;">div>
)}
{attendance === style="color:#9ece6a;">"attending" && (
<style="color:#f7768e;font-weight:600;">div>
<style="color:#f7768e;font-weight:600;">label className=style="color:#9ece6a;">"block text-xs font-medium text-zinc-300 mb-1.5 flex items-center gap-1.5">
<style="color:#f7768e;font-weight:600;">UtensilsCrossed className=style="color:#9ece6a;">"size-3.5 text-amber-400" />
<style="color:#f7768e;font-weight:600;">span>Dietary Requirements</style="color:#f7768e;font-weight:600;">span>
</style="color:#f7768e;font-weight:600;">label>
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"grid grid-cols-4 gap-1.5">
{dietaryOptions.map((opt) => (
<style="color:#f7768e;font-weight:600;">button
key={opt}
type=style="color:#9ece6a;">"button"
onClick={() => setDietary(opt)}
className={`py-1.5 px-1 text-[11px] font-mono rounded-lg transition-all text-center cursor-pointer truncate ${
dietary === opt
? style="color:#9ece6a;">"bg-amber-400 text-zinc-950 font-bold shadow-xs"
: style="color:#9ece6a;">"bg-[#251520] text-zinc-400 border border-pink-500/20 hover:text-white"
}`}
>
{opt}
</style="color:#f7768e;font-weight:600;">button>
))}
</style="color:#f7768e;font-weight:600;">div>
</style="color:#f7768e;font-weight:600;">div>
)}
<style="color:#f7768e;font-weight:600;">div>
<style="color:#f7768e;font-weight:600;">label htmlFor=style="color:#9ece6a;">"songRequest" className=style="color:#9ece6a;">"block text-xs font-medium text-zinc-300 mb-1 flex items-center gap-1.5">
<style="color:#f7768e;font-weight:600;">Music className=style="color:#9ece6a;">"size-3.5 text-pink-400" />
<style="color:#f7768e;font-weight:600;">span>Song Request or Note for Host</style="color:#f7768e;font-weight:600;">span>
</style="color:#f7768e;font-weight:600;">label>
<style="color:#f7768e;font-weight:600;">textarea
id=style="color:#9ece6a;">"songRequest"
name=style="color:#9ece6a;">"songRequest"
rows={2}
placeholder=style="color:#9ece6a;">"What song gets you dancing? Note for Alex?"
value={songRequest}
onChange={(e) => setSongRequest(e.target.value)}
className=style="color:#9ece6a;">"w-full bg-[#251520] text-white border border-pink-500/25 rounded-lg px-3 py-1.5 text-xs placeholder:text-zinc-500 focus:outline-none focus:border-pink-400 transition resize-none"
/>
</style="color:#f7768e;font-weight:600;">div>
<style="color:#f7768e;font-weight:600;">button
type=style="color:#9ece6a;">"submit"
disabled={status === style="color:#9ece6a;">"loading"}
className=style="color:#9ece6a;">"w-full mt-2 bg-gradient-to-r from-pink-500 via-rose-500 to-amber-400 hover:from-pink-400 hover:to-amber-300 text-zinc-950 font-extrabold py-2.5 px-4 rounded-xl transition cursor-pointer shadow-lg shadow-pink-500/25 disabled:opacity-60 flex items-center justify-center gap-2"
>
<style="color:#f7768e;font-weight:600;">PartyPopper className=style="color:#9ece6a;">"size-4 fill-zinc-950" />
<style="color:#f7768e;font-weight:600;">span>{status === style="color:#9ece6a;">"loading" ? style="color:#9ece6a;">"Confirming RSVP..." : style="color:#9ece6a;">"Send Party RSVP 🎉"}</style="color:#f7768e;font-weight:600;">span>
</style="color:#f7768e;font-weight:600;">button>
</style="color:#f7768e;font-weight:600;">form>
)}
</style="color:#f7768e;font-weight:600;">div>
</style="color:#f7768e;font-weight:600;">div>
);
}Connect this component to your Backforms endpoint
Create a free form endpoint in under 30 seconds. Zero backend servers or databases required.
Frequently Asked Questions & Technical Integration
Can Backforms notify me via Telegram when guests RSVP?
Yes! You can link a Telegram bot or Slack channel in your Backforms settings to receive instant ping notifications whenever a guest responds or updates their plus-one details.
How are dietary requirements and guest counts stored?
All fields including dietary choices (vegan, gluten-free, allergies) and plus-one names are captured in structured JSON and automatically synced to your party Google Sheet.
Does this template work for weddings, baby showers, or casual dinners?
Yes! The fields, labels, and color theme can be easily tailored to any social gathering, birthday, wedding, or team dinner.