Appointment request form template
A preferred date and time, a second choice and the reason for the visit — a request you confirm, not a calendar.
Reviewed by Ritu Fadadu · Last reviewed
Preview and code
Preview
Code — replace YOUR_ENDPOINT_ID with your form's ID
<style>
.fb-form { display: grid; gap: 1rem; max-width: 36rem; }
.fb-field { display: flex; flex-direction: column; gap: 0.375rem; }
.fb-fieldset { margin: 0; padding: 0; border: 0; min-width: 0; }
.fb-label { font-weight: 600; }
.fb-optional { font-weight: 400; color: #6b7280; }
.fb-help { margin: 0; font-size: 0.875rem; color: #6b7280; }
.fb-input { box-sizing: border-box; width: 100%; padding: 0.625rem 0.75rem; border: 1px solid #d1d5db; border-radius: 0.5rem; background: #fff; color: #111827; font: inherit; }
.fb-input:focus { border-color: #2563eb; outline: 2px solid #2563eb; outline-offset: 1px; }
.fb-options { display: flex; flex-direction: column; gap: 0.5rem; }
.fb-option { display: flex; align-items: center; gap: 0.5rem; }
.fb-check { width: 1.125rem; height: 1.125rem; margin: 0; accent-color: #2563eb; }
.fb-button { justify-self: start; min-height: 44px; padding: 0.625rem 1.25rem; border: 0; border-radius: 0.5rem; background: #2563eb; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
.fb-button:hover { background: #1d4ed8; }
.fb-button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
.fb-button:disabled { opacity: 0.6; cursor: wait; }
.fb-status { margin: 0; min-height: 1.5em; }
@media (min-width: 640px) {
.fb-form { grid-template-columns: 1fr 1fr; }
.fb-field, .fb-button, .fb-status { grid-column: 1 / -1; }
.fb-field--half { grid-column: auto; }
}
</style>
<form id="fb-form" class="fb-form" action="https://formboost.app/f/YOUR_ENDPOINT_ID" method="POST">
<div class="fb-field fb-field--half">
<label for="fb-name" class="fb-label">Full name</label>
<input type="text" id="fb-name" name="name" class="fb-input" required autocomplete="name">
</div>
<div class="fb-field">
<label for="fb-email" class="fb-label">Email</label>
<input type="email" id="fb-email" name="email" class="fb-input" required autocomplete="email">
</div>
<div class="fb-field fb-field--half">
<label for="fb-phone" class="fb-label">Phone <span class="fb-optional">(optional)</span></label>
<input type="tel" id="fb-phone" name="phone" class="fb-input" autocomplete="tel">
</div>
<div class="fb-field fb-field--half">
<label for="fb-service" class="fb-label">Type of appointment</label>
<select id="fb-service" name="service" class="fb-input" required>
<option value="">Choose…</option>
<option value="consultation">Consultation</option>
<option value="follow-up">Follow-up</option>
<option value="something-else">Something else</option>
</select>
</div>
<div class="fb-field fb-field--half">
<label for="fb-date" class="fb-label">Preferred date</label>
<input type="date" id="fb-date" name="date" class="fb-input" required>
</div>
<div class="fb-field fb-field--half">
<label for="fb-time" class="fb-label">Preferred time</label>
<input type="time" id="fb-time" name="time" class="fb-input" required>
</div>
<div class="fb-field">
<label for="fb-alternative" class="fb-label">
A second choice of date and time
<span class="fb-optional">(optional)</span>
</label>
<input
type="text"
id="fb-alternative"
name="alternative"
class="fb-input"
placeholder="e.g. Thursday afternoon"
>
</div>
<div class="fb-field">
<label for="fb-notes" class="fb-label">
Anything we should know beforehand?
<span class="fb-optional">(optional)</span>
</label>
<textarea id="fb-notes" name="notes" class="fb-input" rows="3"></textarea>
</div>
<!-- Honeypot: keep it hidden. People never see it; bots fill it in and get flagged as spam. -->
<div
aria-hidden="true"
style="position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden"
>
<label for="fb-honey">Leave this field empty</label>
<input type="text" id="fb-honey" name="_honey" tabindex="-1" autocomplete="off">
</div>
<input type="hidden" name="_subject" value="New appointment request">
<button type="submit" class="fb-button">Request appointment</button>
</form>
<style>
.fb-form { display: grid; gap: 1rem; max-width: 36rem; }
.fb-field { display: flex; flex-direction: column; gap: 0.375rem; }
.fb-fieldset { margin: 0; padding: 0; border: 0; min-width: 0; }
.fb-label { font-weight: 600; }
.fb-optional { font-weight: 400; color: #6b7280; }
.fb-help { margin: 0; font-size: 0.875rem; color: #6b7280; }
.fb-input { box-sizing: border-box; width: 100%; padding: 0.625rem 0.75rem; border: 1px solid #d1d5db; border-radius: 0.5rem; background: #fff; color: #111827; font: inherit; }
.fb-input:focus { border-color: #2563eb; outline: 2px solid #2563eb; outline-offset: 1px; }
.fb-options { display: flex; flex-direction: column; gap: 0.5rem; }
.fb-option { display: flex; align-items: center; gap: 0.5rem; }
.fb-check { width: 1.125rem; height: 1.125rem; margin: 0; accent-color: #2563eb; }
.fb-button { justify-self: start; min-height: 44px; padding: 0.625rem 1.25rem; border: 0; border-radius: 0.5rem; background: #2563eb; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
.fb-button:hover { background: #1d4ed8; }
.fb-button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
.fb-button:disabled { opacity: 0.6; cursor: wait; }
.fb-status { margin: 0; min-height: 1.5em; }
@media (min-width: 640px) {
.fb-form { grid-template-columns: 1fr 1fr; }
.fb-field, .fb-button, .fb-status { grid-column: 1 / -1; }
.fb-field--half { grid-column: auto; }
}
</style>
<form id="fb-form" class="fb-form" action="https://formboost.app/f/YOUR_ENDPOINT_ID" method="POST">
<div class="fb-field fb-field--half">
<label for="fb-name" class="fb-label">Full name</label>
<input type="text" id="fb-name" name="name" class="fb-input" required autocomplete="name">
</div>
<div class="fb-field">
<label for="fb-email" class="fb-label">Email</label>
<input type="email" id="fb-email" name="email" class="fb-input" required autocomplete="email">
</div>
<div class="fb-field fb-field--half">
<label for="fb-phone" class="fb-label">Phone <span class="fb-optional">(optional)</span></label>
<input type="tel" id="fb-phone" name="phone" class="fb-input" autocomplete="tel">
</div>
<div class="fb-field fb-field--half">
<label for="fb-service" class="fb-label">Type of appointment</label>
<select id="fb-service" name="service" class="fb-input" required>
<option value="">Choose…</option>
<option value="consultation">Consultation</option>
<option value="follow-up">Follow-up</option>
<option value="something-else">Something else</option>
</select>
</div>
<div class="fb-field fb-field--half">
<label for="fb-date" class="fb-label">Preferred date</label>
<input type="date" id="fb-date" name="date" class="fb-input" required>
</div>
<div class="fb-field fb-field--half">
<label for="fb-time" class="fb-label">Preferred time</label>
<input type="time" id="fb-time" name="time" class="fb-input" required>
</div>
<div class="fb-field">
<label for="fb-alternative" class="fb-label">
A second choice of date and time
<span class="fb-optional">(optional)</span>
</label>
<input
type="text"
id="fb-alternative"
name="alternative"
class="fb-input"
placeholder="e.g. Thursday afternoon"
>
</div>
<div class="fb-field">
<label for="fb-notes" class="fb-label">
Anything we should know beforehand?
<span class="fb-optional">(optional)</span>
</label>
<textarea id="fb-notes" name="notes" class="fb-input" rows="3"></textarea>
</div>
<!-- Honeypot: keep it hidden. People never see it; bots fill it in and get flagged as spam. -->
<div
aria-hidden="true"
style="position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden"
>
<label for="fb-honey">Leave this field empty</label>
<input type="text" id="fb-honey" name="_honey" tabindex="-1" autocomplete="off">
</div>
<input type="hidden" name="_subject" value="New appointment request">
<button type="submit" class="fb-button">Request appointment</button>
<p class="fb-status" role="status" aria-live="polite" data-fb-status></p>
</form>
<script>
document.getElementById("fb-form").addEventListener("submit", async (event) => {
event.preventDefault();
const form = event.currentTarget;
const status = form.querySelector("[data-fb-status]");
const button = form.querySelector('button[type="submit"]');
const fields = new FormData(form);
// Formboost accepts JSON or url-encoded bodies, not multipart, so send JSON.
const data = Object.fromEntries(fields);
// Makes the notification email replyable; the address still shows in the field table.
if (data.email) data._replyto = data.email;
button.disabled = true;
status.textContent = "Sending…";
try {
const response = await fetch(form.action, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(data),
});
if (response.ok) {
form.reset();
status.textContent = "Thanks — we'll confirm your appointment by email.";
} else if (response.status === 429) {
status.textContent = "Too many attempts. Please wait a minute and try again.";
} else {
status.textContent = "Something went wrong. Please try again.";
}
} catch {
status.textContent = "Could not send. Check your connection and try again.";
} finally {
button.disabled = false;
}
});
</script>
import { useState } from "react";
// Your form's endpoint, from the Formboost dashboard.
const ENDPOINT = "https://formboost.app/f/YOUR_ENDPOINT_ID";
export default function AppointmentRequestForm() {
const [sending, setSending] = useState(false);
const [message, setMessage] = useState("");
async function handleSubmit(event) {
event.preventDefault();
const form = event.currentTarget;
const fields = new FormData(form);
// Formboost accepts JSON or url-encoded bodies, not multipart, so send JSON.
const data = Object.fromEntries(fields);
// Makes the notification email replyable; the address still shows in the field table.
if (data.email) data._replyto = data.email;
setSending(true);
setMessage("Sending…");
try {
const response = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(data),
});
if (response.ok) {
form.reset();
setMessage("Thanks — we'll confirm your appointment by email.");
} else if (response.status === 429) {
setMessage("Too many attempts. Please wait a minute and try again.");
} else {
setMessage("Something went wrong. Please try again.");
}
} catch {
setMessage("Could not send. Check your connection and try again.");
} finally {
setSending(false);
}
}
return (
<form
className="grid max-w-xl gap-4 sm:grid-cols-2"
action={ENDPOINT}
method="POST"
onSubmit={handleSubmit}
>
<div className="flex flex-col gap-1.5">
<label htmlFor="fb-name" className="text-sm font-semibold text-gray-900">Full name</label>
<input
type="text"
id="fb-name"
name="name"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
autoComplete="name"
/>
</div>
<div className="flex flex-col gap-1.5 sm:col-span-2">
<label htmlFor="fb-email" className="text-sm font-semibold text-gray-900">Email</label>
<input
type="email"
id="fb-email"
name="email"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
autoComplete="email"
/>
</div>
<div className="flex flex-col gap-1.5">
<label htmlFor="fb-phone" className="text-sm font-semibold text-gray-900">
Phone
<span className="font-normal text-gray-500">(optional)</span>
</label>
<input
type="tel"
id="fb-phone"
name="phone"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
autoComplete="tel"
/>
</div>
<div className="flex flex-col gap-1.5">
<label htmlFor="fb-service" className="text-sm font-semibold text-gray-900">
Type of appointment
</label>
<select
id="fb-service"
name="service"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
>
<option value="">Choose…</option>
<option value="consultation">Consultation</option>
<option value="follow-up">Follow-up</option>
<option value="something-else">Something else</option>
</select>
</div>
<div className="flex flex-col gap-1.5">
<label htmlFor="fb-date" className="text-sm font-semibold text-gray-900">
Preferred date
</label>
<input
type="date"
id="fb-date"
name="date"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
/>
</div>
<div className="flex flex-col gap-1.5">
<label htmlFor="fb-time" className="text-sm font-semibold text-gray-900">
Preferred time
</label>
<input
type="time"
id="fb-time"
name="time"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
/>
</div>
<div className="flex flex-col gap-1.5 sm:col-span-2">
<label htmlFor="fb-alternative" className="text-sm font-semibold text-gray-900">
A second choice of date and time
<span className="font-normal text-gray-500">(optional)</span>
</label>
<input
type="text"
id="fb-alternative"
name="alternative"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
placeholder="e.g. Thursday afternoon"
/>
</div>
<div className="flex flex-col gap-1.5 sm:col-span-2">
<label htmlFor="fb-notes" className="text-sm font-semibold text-gray-900">
Anything we should know beforehand?
<span className="font-normal text-gray-500">(optional)</span>
</label>
<textarea
id="fb-notes"
name="notes"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
rows={3}
></textarea>
</div>
{/* Honeypot: keep it hidden. People never see it; bots fill it in and get flagged as spam. */}
<div
aria-hidden="true"
style={{ position: "absolute", left: "-9999px", width: "1px", height: "1px", overflow: "hidden" }}
>
<label htmlFor="fb-honey">Leave this field empty</label>
<input type="text" id="fb-honey" name="_honey" tabIndex={-1} autoComplete="off" />
</div>
<input type="hidden" name="_subject" defaultValue="New appointment request" />
<button
type="submit"
className="min-h-11 justify-self-start rounded-lg bg-blue-600 px-5 py-2.5 font-semibold text-white hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 disabled:opacity-60 sm:col-span-2"
disabled={sending}
>
Request appointment
</button>
<p className="min-h-6 text-sm text-gray-700 sm:col-span-2" role="status" aria-live="polite">
{message}
</p>
</form>
);
}
<form
id="fb-form"
class="grid max-w-xl gap-4 sm:grid-cols-2"
action="https://formboost.app/f/YOUR_ENDPOINT_ID"
method="POST"
>
<div class="flex flex-col gap-1.5">
<label for="fb-name" class="text-sm font-semibold text-gray-900">Full name</label>
<input
type="text"
id="fb-name"
name="name"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
autocomplete="name"
>
</div>
<div class="flex flex-col gap-1.5 sm:col-span-2">
<label for="fb-email" class="text-sm font-semibold text-gray-900">Email</label>
<input
type="email"
id="fb-email"
name="email"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
autocomplete="email"
>
</div>
<div class="flex flex-col gap-1.5">
<label for="fb-phone" class="text-sm font-semibold text-gray-900">
Phone
<span class="font-normal text-gray-500">(optional)</span>
</label>
<input
type="tel"
id="fb-phone"
name="phone"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
autocomplete="tel"
>
</div>
<div class="flex flex-col gap-1.5">
<label for="fb-service" class="text-sm font-semibold text-gray-900">Type of appointment</label>
<select
id="fb-service"
name="service"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
>
<option value="">Choose…</option>
<option value="consultation">Consultation</option>
<option value="follow-up">Follow-up</option>
<option value="something-else">Something else</option>
</select>
</div>
<div class="flex flex-col gap-1.5">
<label for="fb-date" class="text-sm font-semibold text-gray-900">Preferred date</label>
<input
type="date"
id="fb-date"
name="date"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
>
</div>
<div class="flex flex-col gap-1.5">
<label for="fb-time" class="text-sm font-semibold text-gray-900">Preferred time</label>
<input
type="time"
id="fb-time"
name="time"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
>
</div>
<div class="flex flex-col gap-1.5 sm:col-span-2">
<label for="fb-alternative" class="text-sm font-semibold text-gray-900">
A second choice of date and time
<span class="font-normal text-gray-500">(optional)</span>
</label>
<input
type="text"
id="fb-alternative"
name="alternative"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
placeholder="e.g. Thursday afternoon"
>
</div>
<div class="flex flex-col gap-1.5 sm:col-span-2">
<label for="fb-notes" class="text-sm font-semibold text-gray-900">
Anything we should know beforehand?
<span class="font-normal text-gray-500">(optional)</span>
</label>
<textarea
id="fb-notes"
name="notes"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
rows="3"
></textarea>
</div>
<!-- Honeypot: keep it hidden. People never see it; bots fill it in and get flagged as spam. -->
<div
aria-hidden="true"
style="position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden"
>
<label for="fb-honey">Leave this field empty</label>
<input type="text" id="fb-honey" name="_honey" tabindex="-1" autocomplete="off">
</div>
<input type="hidden" name="_subject" value="New appointment request">
<button
type="submit"
class="min-h-11 justify-self-start rounded-lg bg-blue-600 px-5 py-2.5 font-semibold text-white hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 disabled:opacity-60 sm:col-span-2"
>
Request appointment
</button>
</form>
About this template
This form collects a request, not a booking. It does not check your calendar or hold a slot; it gives you a preferred date and time plus a second choice, and you confirm one. For a small practice, a studio or a consultant that is often all the scheduling needed, and it keeps double-booking impossible because you are the one confirming.
Date and time use the browser's native pickers, which are accessible and need no JavaScript library. Asking for a second choice up front saves the back-and-forth when the first one is taken.
Say on the page how you will confirm, and how soon. A custom redirect to a “we'll confirm within one working day” page sets that expectation; on Starter and above, Auto Reply sends it by email too.
Fields in this template
| Field | Name | Type | Required | Why it is there |
|---|---|---|---|---|
| Full name | name | Short text | Yes | Required. |
email | Yes | Required: it is where the confirmation goes. | ||
| Phone | phone | Phone | No | Optional, for last-minute changes. |
| Type of appointment | service | Dropdown | Yes | Required, so you know how long to book. |
| Preferred date | date | Date | Yes | Required native date picker. |
| Preferred time | time | Time | Yes | Required native time picker. |
| A second choice of date and time | alternative | Short text | No | Optional free text — easier to answer than a second pair of pickers. |
| Anything we should know beforehand? | notes | Long text | No | Optional context before the appointment. |
Each name becomes a column in your dashboard and a row in the notification email. The hidden _honey field is the honeypot; _subject sets the email's subject line. Both are reserved fields and never appear in the field table.
Make it yours
- Add
minto the date field (for example tomorrow's date, set from JavaScript) to stop requests for the past. - List your opening hours next to the form; the time picker does not know them.
- Rename the service options to your own and keep them few — each is a different length of slot.
- If you work from more than one location, add a single-choice
locationquestion above the date, so every request names the place as well as the time and nobody turns up at the wrong door.
Where the submissions go
- Each request is stored and emailed to you. With the HTML + JS or React version, replying to the notification goes straight to the person who asked.
- A Telegram or Slack message per request helps you confirm quickly, on any plan.
- On Starter and above, Auto Reply acknowledges the request immediately, and a webhook can pass it to your calendar tool.
Before you publish it
- Create a form in the Formboost dashboard and replace
YOUR_ENDPOINT_IDwith its ID. - Pick one variant: HTML posts and reloads, HTML + JS and React send JSON with
fetch()and stay on the page. - For the plain HTML version, add a hidden
_redirectwith anhttps://thank-you page on the same site as the form — anything else falls back to Formboost's page. - Submit it once from the live page and check the submission in your dashboard. Every request is acknowledged, so the dashboard is the real test. Troubleshooting covers the rest.
More form templates
Quote request form template
Service, budget range, timeline and the brief — enough to reply with a real number.
Event registration form template
Attendee details, how they will attend, and the two questions organisers always forget to ask.
Support ticket form template
Category, urgency, where it happened and how to reproduce it — the details a first reply usually has to ask for.
All form templates
Every template, in HTML, React and Tailwind.
Put the appointment request form live
Free forever: 500 submissions a month across 10 forms, no card.