Skip to main content

Restaurant Table Reservation Form

A sophisticated restaurant and dining reservation form styled with Tailwind CSS. Captures party size, reservation date, seating timeslot, preferred dining area (patio, booth, main room), guest contact details, and dietary notes. Pre-wired for Backforms with instant SMS and host notifications.

Party size selector buttons (1 to 6+ guests)
Date picker & seating timeslot selector
Seating area preference (Patio, Booth, Dining Room)
Special requests & dietary requirement notes
Invisible honeypot (_gotcha) spam trap
Live Form PreviewComponent Sandbox

Reserve a Table

Select your guest count, preferred seating time, and any dietary preferences or special requests.

Pre-configured for Backforms endpoint with instant SMS and host notifications
Honeypot anti-spam active
Zero backend needed
style="color:#9ece6a;">"use client";

import { useState } from style="color:#9ece6a;">"react";
import { Utensils, Check, AlertCircle, Calendar, Clock, Users } from style="color:#9ece6a;">"lucide-react";

export default function RestaurantReservationForm() {
  const [formData, setFormData] = useState({
    guests: style="color:#9ece6a;">"2",
    date: style="color:#9ece6a;">"2026-11-20",
    time: style="color:#9ece6a;">"19:30",
    seating: style="color:#9ece6a;">"Main Dining",
    name: style="color:#9ece6a;">"",
    phone: style="color:#9ece6a;">"",
    email: style="color:#9ece6a;">"",
    specialRequests: 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 [resCode, setResCode] = useState(style="color:#9ece6a;">"");
  const [errorMessage, setErrorMessage] = useState(style="color:#9ece6a;">"");

  const guestCounts = [style="color:#9ece6a;">"1", style="color:#9ece6a;">"2", style="color:#9ece6a;">"3", style="color:#9ece6a;">"4", style="color:#9ece6a;">"5", style="color:#9ece6a;">"6+"];
  const timeSlots = [style="color:#9ece6a;">"17:30", style="color:#9ece6a;">"18:00", style="color:#9ece6a;">"18:30", style="color:#9ece6a;">"19:00", style="color:#9ece6a;">"19:30", style="color:#9ece6a;">"20:00", style="color:#9ece6a;">"20:30", style="color:#9ece6a;">"21:00"];
  const seatingOptions = [style="color:#9ece6a;">"Main Dining", style="color:#9ece6a;">"Outdoor Patio", style="color:#9ece6a;">"Booth Seating", style="color:#9ece6a;">"Chef's Counter"];

  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 body = new FormData(form);

    try {
      style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">// Replace YOUR_FORM_ID with your actual endpoint ID from https://app.backforms.com
      const res = 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,
      });

      if (res.ok) {
        setResCode(`RES-${Math.floor(1000 + Math.random() * 9000)}`);
        setStatus(style="color:#9ece6a;">"success");
      } else {
        const data = await res.json().catch(() => ({}));
        setErrorMessage(data.error || style="color:#9ece6a;">"Failed to confirm reservation. Please try again.");
        setStatus(style="color:#9ece6a;">"error");
      }
    } catch {
      setErrorMessage(style="color:#9ece6a;">"Network error occurred. Please check your connection.");
      setStatus(style="color:#9ece6a;">"error");
    }
  }

  if (status === style="color:#9ece6a;">"success") {
    return (
      <style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"max-w-xl mx-auto p-8 bg-[#151413] border border-zinc-800 rounded-2xl text-center space-y-3">
        <style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"size-10 mx-auto rounded-full bg-amber-950/60 border border-amber-500/30 flex items-center justify-center text-amber-400">
          <style="color:#f7768e;font-weight:600;">Check className=style="color:#9ece6a;">"size-5 stroke-[2.5]" />
        </style="color:#f7768e;font-weight:600;">div>
        <style="color:#f7768e;font-weight:600;">h3 className=style="color:#9ece6a;">"text-xl font-bold text-white">Table Reserved (#{resCode})</style="color:#f7768e;font-weight:600;">h3>
        <style="color:#f7768e;font-weight:600;">p className=style="color:#9ece6a;">"text-xs text-zinc-400 max-w-sm mx-auto">
          We have reserved a table for {formData.guests} guests on {formData.date} at {formData.time}. A confirmation SMS has been dispatched.
        </style="color:#f7768e;font-weight:600;">p>
        <style="color:#f7768e;font-weight:600;">button
          onClick={() => {
            setFormData({ guests: style="color:#9ece6a;">"2", date: style="color:#9ece6a;">"2026-11-20", time: style="color:#9ece6a;">"19:30", seating: style="color:#9ece6a;">"Main Dining", name: style="color:#9ece6a;">"", phone: style="color:#9ece6a;">"", email: style="color:#9ece6a;">"", specialRequests: style="color:#9ece6a;">"" });
            setStatus(style="color:#9ece6a;">"idle");
          }}
          className=style="color:#9ece6a;">"mt-3 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-white text-xs font-medium rounded-lg"
        >
          Book Another Reservation
        </style="color:#f7768e;font-weight:600;">button>
      </style="color:#f7768e;font-weight:600;">div>
    );
  }

  return (
    <style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"max-w-xl mx-auto p-6 sm:p-8 bg-[#151413] border border-zinc-800 rounded-2xl text-zinc-200">
      <style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"mb-6">
        <style="color:#f7768e;font-weight:600;">h2 className=style="color:#9ece6a;">"text-2xl font-bold text-white tracking-tight">Reserve a Table</style="color:#f7768e;font-weight:600;">h2>
        <style="color:#f7768e;font-weight:600;">p className=style="color:#9ece6a;">"text-xs text-zinc-400 mt-1">Book dinner seating and let us know about any special requests.</style="color:#f7768e;font-weight:600;">p>
      </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" />

        {style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">/* Party Size */}
        <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;">Users className=style="color:#9ece6a;">"size-3.5 text-amber-400" />
            <style="color:#f7768e;font-weight:600;">span>Number of Guests</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-6 gap-2">
            {guestCounts.map((count) => (
              <style="color:#f7768e;font-weight:600;">button
                key={count}
                type=style="color:#9ece6a;">"button"
                onClick={() => setFormData({ ...formData, guests: count })}
                className={`py-2 text-xs font-medium rounded-lg border text-center transition-colors cursor-pointer ${
                  formData.guests === count
                    ? style="color:#9ece6a;">"bg-amber-400 text-zinc-950 border-amber-400 font-bold shadow-xs"
                    : style="color:#9ece6a;">"bg-[#1c1b19] text-zinc-300 border-zinc-800 hover:border-zinc-700 hover:text-white"
                }`}
              >
                {count}
              </style="color:#f7768e;font-weight:600;">button>
            ))}
          </style="color:#f7768e;font-weight:600;">div>
          <style="color:#f7768e;font-weight:600;">input type=style="color:#9ece6a;">"hidden" name=style="color:#9ece6a;">"partySize" value={formData.guests} />
        </style="color:#f7768e;font-weight:600;">div>

        {style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">/* Date & Time */}
        <style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"grid grid-cols-1 sm:grid-cols-2 gap-3.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 flex items-center gap-1.5">
              <style="color:#f7768e;font-weight:600;">Calendar className=style="color:#9ece6a;">"size-3.5 text-amber-400" />
              <style="color:#f7768e;font-weight:600;">span>Reservation Date</style="color:#f7768e;font-weight:600;">span>
            </style="color:#f7768e;font-weight:600;">label>
            <style="color:#f7768e;font-weight:600;">input
              type=style="color:#9ece6a;">"date"
              name=style="color:#9ece6a;">"reservationDate"
              required
              value={formData.date}
              onChange={(e) => setFormData({ ...formData, date: e.target.value })}
              className=style="color:#9ece6a;">"w-full bg-[#1c1b19] text-zinc-100 border border-zinc-800 rounded-lg px-3 py-2 text-xs focus:outline-none focus:border-amber-400 transition-colors"
            />
          </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 flex items-center gap-1.5">
              <style="color:#f7768e;font-weight:600;">Clock className=style="color:#9ece6a;">"size-3.5 text-amber-400" />
              <style="color:#f7768e;font-weight:600;">span>Seating Time</style="color:#f7768e;font-weight:600;">span>
            </style="color:#f7768e;font-weight:600;">label>
            <style="color:#f7768e;font-weight:600;">select
              name=style="color:#9ece6a;">"seatingTime"
              value={formData.time}
              onChange={(e) => setFormData({ ...formData, time: e.target.value })}
              className=style="color:#9ece6a;">"w-full bg-[#1c1b19] text-zinc-100 border border-zinc-800 rounded-lg px-3 py-2 text-xs focus:outline-none focus:border-amber-400 transition-colors"
            >
              {timeSlots.map((slot) => (
                <style="color:#f7768e;font-weight:600;">option key={slot} value={slot}>{slot}</style="color:#f7768e;font-weight:600;">option>
              ))}
            </style="color:#f7768e;font-weight:600;">select>
          </style="color:#f7768e;font-weight:600;">div>
        </style="color:#f7768e;font-weight:600;">div>

        {style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">/* Seating Area */}
        <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">Seating Area Preference</style="color:#f7768e;font-weight:600;">label>
          <style="color:#f7768e;font-weight:600;">select
            name=style="color:#9ece6a;">"seatingPreference"
            value={formData.seating}
            onChange={(e) => setFormData({ ...formData, seating: e.target.value })}
            className=style="color:#9ece6a;">"w-full bg-[#1c1b19] text-zinc-100 border border-zinc-800 rounded-lg px-3 py-2 text-xs focus:outline-none focus:border-amber-400 transition-colors"
          >
            {seatingOptions.map((opt) => (
              <style="color:#f7768e;font-weight:600;">option key={opt} value={opt}>{opt}</style="color:#f7768e;font-weight:600;">option>
            ))}
          </style="color:#f7768e;font-weight:600;">select>
        </style="color:#f7768e;font-weight:600;">div>

        {style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">/* Guest Details */}
        <style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"grid grid-cols-1 sm:grid-cols-3 gap-3">
          <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">Primary Guest Name</style="color:#f7768e;font-weight:600;">label>
            <style="color:#f7768e;font-weight:600;">input
              type=style="color:#9ece6a;">"text"
              name=style="color:#9ece6a;">"guestName"
              required
              placeholder=style="color:#9ece6a;">"Elena Rostova"
              value={formData.name}
              onChange={(e) => setFormData({ ...formData, name: e.target.value })}
              className=style="color:#9ece6a;">"w-full bg-[#1c1b19] text-zinc-100 border border-zinc-800 rounded-lg px-3 py-2 text-xs placeholder:text-zinc-500 focus:outline-none focus:border-amber-400 transition-colors"
            />
          </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">Phone Number (SMS)</style="color:#f7768e;font-weight:600;">label>
            <style="color:#f7768e;font-weight:600;">input
              type=style="color:#9ece6a;">"tel"
              name=style="color:#9ece6a;">"phoneNumber"
              required
              placeholder=style="color:#9ece6a;">"+1 (555) 234-5678"
              value={formData.phone}
              onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
              className=style="color:#9ece6a;">"w-full bg-[#1c1b19] text-zinc-100 border border-zinc-800 rounded-lg px-3 py-2 text-xs placeholder:text-zinc-500 focus:outline-none focus:border-amber-400 transition-colors"
            />
          </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">Email Address</style="color:#f7768e;font-weight:600;">label>
            <style="color:#f7768e;font-weight:600;">input
              type=style="color:#9ece6a;">"email"
              name=style="color:#9ece6a;">"emailAddress"
              required
              placeholder=style="color:#9ece6a;">"elena@example.com"
              value={formData.email}
              onChange={(e) => setFormData({ ...formData, email: e.target.value })}
              className=style="color:#9ece6a;">"w-full bg-[#1c1b19] text-zinc-100 border border-zinc-800 rounded-lg px-3 py-2 text-xs placeholder:text-zinc-500 focus:outline-none focus:border-amber-400 transition-colors"
            />
          </style="color:#f7768e;font-weight:600;">div>
        </style="color:#f7768e;font-weight:600;">div>

        {style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">/* Special Requests */}
        <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">Special Requests or Dietary Requirements</style="color:#f7768e;font-weight:600;">label>
          <style="color:#f7768e;font-weight:600;">textarea
            name=style="color:#9ece6a;">"specialRequests"
            rows={2}
            placeholder=style="color:#9ece6a;">"Anniversary celebration, allergy notes, quiet booth, high chair..."
            value={formData.specialRequests}
            onChange={(e) => setFormData({ ...formData, specialRequests: e.target.value })}
            className=style="color:#9ece6a;">"w-full bg-[#1c1b19] text-zinc-100 border border-zinc-800 rounded-lg px-3 py-2 text-xs placeholder:text-zinc-500 focus:outline-none focus:border-amber-400 transition-colors resize-none"
          />
        </style="color:#f7768e;font-weight:600;">div>

        {status === style="color:#9ece6a;">"error" && (
          <style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"p-3 bg-red-950/40 border border-red-500/30 rounded-lg flex items-center gap-2 text-xs text-red-300">
            <style="color:#f7768e;font-weight:600;">AlertCircle className=style="color:#9ece6a;">"size-4 shrink-0" />
            <style="color:#f7768e;font-weight:600;">span>{errorMessage}</style="color:#f7768e;font-weight:600;">span>
          </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-amber-400 hover:bg-amber-300 text-zinc-950 font-semibold text-xs py-2.5 px-4 rounded-lg active:scale-[0.99] transition duration-150 cursor-pointer disabled:opacity-60 flex items-center justify-center gap-2"
        >
          <style="color:#f7768e;font-weight:600;">Utensils className=style="color:#9ece6a;">"size-4 shrink-0" />
          <style="color:#f7768e;font-weight:600;">span>{status === style="color:#9ece6a;">"loading" ? style="color:#9ece6a;">"Confirming Table..." : style="color:#9ece6a;">"Confirm Table Reservation"}</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>
  );
}
Replace YOUR_FORM_ID with your Backforms key

Connect this component to your Backforms endpoint

Create a free form endpoint in under 30 seconds. Zero backend servers or databases required.

Get Free Endpoint Key

Frequently Asked Questions & Technical Integration

Can Backforms send instant SMS or WhatsApp notifications to our host stand?

Yes! Backforms webhooks integrate seamlessly with Twilio, Telegram, and Slack, ensuring your maître d' and kitchen staff receive instant booking pings with party size and dietary notes.

How can we block large parties that require a deposit?

You can configure your form options to allow bookings up to 6 guests online, and provide a phone prompt for larger private dining events.

Are dietary preferences and allergy notes captured in our spreadsheet?

Yes! All submitted fields including dietary requirements (gluten-free, peanut allergy, vegan) and seating preferences are automatically mapped to your Google Sheet or restaurant management database.