Feedback form template
A rating, two open questions and the page it came from — short enough that people finish it.
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">
<fieldset class="fb-field fb-fieldset">
<legend class="fb-label">How would you rate this page?</legend>
<div class="fb-options">
<label class="fb-option">
<input type="radio" name="rating" value="1" class="fb-check" required>
<span>1 — Poor</span>
</label>
<label class="fb-option">
<input type="radio" name="rating" value="2" class="fb-check" required>
<span>2</span>
</label>
<label class="fb-option">
<input type="radio" name="rating" value="3" class="fb-check" required>
<span>3</span>
</label>
<label class="fb-option">
<input type="radio" name="rating" value="4" class="fb-check" required>
<span>4</span>
</label>
<label class="fb-option">
<input type="radio" name="rating" value="5" class="fb-check" required>
<span>5 — Excellent</span>
</label>
</div>
</fieldset>
<div class="fb-field">
<label for="fb-worked" class="fb-label">
What worked well?
<span class="fb-optional">(optional)</span>
</label>
<textarea id="fb-worked" name="worked" class="fb-input" rows="3"></textarea>
</div>
<div class="fb-field">
<label for="fb-improve" class="fb-label">
What should we improve?
<span class="fb-optional">(optional)</span>
</label>
<textarea id="fb-improve" name="improve" class="fb-input" rows="3"></textarea>
</div>
<div class="fb-field">
<label for="fb-email" class="fb-label">Email <span class="fb-optional">(optional)</span></label>
<input
type="email"
id="fb-email"
name="email"
class="fb-input"
autocomplete="email"
aria-describedby="fb-email-help"
>
<p id="fb-email-help" class="fb-help">Only if you would like a reply.</p>
</div>
<input type="hidden" name="page" value="/your-page">
<!-- 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 feedback">
<button type="submit" class="fb-button">Send feedback</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">
<fieldset class="fb-field fb-fieldset">
<legend class="fb-label">How would you rate this page?</legend>
<div class="fb-options">
<label class="fb-option">
<input type="radio" name="rating" value="1" class="fb-check" required>
<span>1 — Poor</span>
</label>
<label class="fb-option">
<input type="radio" name="rating" value="2" class="fb-check" required>
<span>2</span>
</label>
<label class="fb-option">
<input type="radio" name="rating" value="3" class="fb-check" required>
<span>3</span>
</label>
<label class="fb-option">
<input type="radio" name="rating" value="4" class="fb-check" required>
<span>4</span>
</label>
<label class="fb-option">
<input type="radio" name="rating" value="5" class="fb-check" required>
<span>5 — Excellent</span>
</label>
</div>
</fieldset>
<div class="fb-field">
<label for="fb-worked" class="fb-label">
What worked well?
<span class="fb-optional">(optional)</span>
</label>
<textarea id="fb-worked" name="worked" class="fb-input" rows="3"></textarea>
</div>
<div class="fb-field">
<label for="fb-improve" class="fb-label">
What should we improve?
<span class="fb-optional">(optional)</span>
</label>
<textarea id="fb-improve" name="improve" class="fb-input" rows="3"></textarea>
</div>
<div class="fb-field">
<label for="fb-email" class="fb-label">Email <span class="fb-optional">(optional)</span></label>
<input
type="email"
id="fb-email"
name="email"
class="fb-input"
autocomplete="email"
aria-describedby="fb-email-help"
>
<p id="fb-email-help" class="fb-help">Only if you would like a reply.</p>
</div>
<input type="hidden" name="page" value="/your-page">
<!-- 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 feedback">
<button type="submit" class="fb-button">Send feedback</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 = "Thank you — your feedback has been sent.";
} 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 FeedbackForm() {
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("Thank you — your feedback has been sent.");
} 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}
>
<fieldset className="flex flex-col gap-1.5 sm:col-span-2 m-0 min-w-0 border-0 p-0">
<legend className="text-sm font-semibold text-gray-900">
How would you rate this page?
</legend>
<div className="flex flex-col gap-2">
<label className="flex items-center gap-2 text-gray-800">
<input
type="radio"
name="rating"
value="1"
className="h-4 w-4 accent-blue-600"
required
/>
<span>1 — Poor</span>
</label>
<label className="flex items-center gap-2 text-gray-800">
<input
type="radio"
name="rating"
value="2"
className="h-4 w-4 accent-blue-600"
required
/>
<span>2</span>
</label>
<label className="flex items-center gap-2 text-gray-800">
<input
type="radio"
name="rating"
value="3"
className="h-4 w-4 accent-blue-600"
required
/>
<span>3</span>
</label>
<label className="flex items-center gap-2 text-gray-800">
<input
type="radio"
name="rating"
value="4"
className="h-4 w-4 accent-blue-600"
required
/>
<span>4</span>
</label>
<label className="flex items-center gap-2 text-gray-800">
<input
type="radio"
name="rating"
value="5"
className="h-4 w-4 accent-blue-600"
required
/>
<span>5 — Excellent</span>
</label>
</div>
</fieldset>
<div className="flex flex-col gap-1.5 sm:col-span-2">
<label htmlFor="fb-worked" className="text-sm font-semibold text-gray-900">
What worked well?
<span className="font-normal text-gray-500">(optional)</span>
</label>
<textarea
id="fb-worked"
name="worked"
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>
<div className="flex flex-col gap-1.5 sm:col-span-2">
<label htmlFor="fb-improve" className="text-sm font-semibold text-gray-900">
What should we improve?
<span className="font-normal text-gray-500">(optional)</span>
</label>
<textarea
id="fb-improve"
name="improve"
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>
<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
<span className="font-normal text-gray-500">(optional)</span>
</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"
autoComplete="email"
aria-describedby="fb-email-help"
/>
<p id="fb-email-help" className="text-sm text-gray-500">Only if you would like a reply.</p>
</div>
<input type="hidden" name="page" defaultValue="/your-page" />
{/* 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 feedback" />
<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}
>
Send feedback
</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"
>
<fieldset class="flex flex-col gap-1.5 sm:col-span-2 m-0 min-w-0 border-0 p-0">
<legend class="text-sm font-semibold text-gray-900">How would you rate this page?</legend>
<div class="flex flex-col gap-2">
<label class="flex items-center gap-2 text-gray-800">
<input type="radio" name="rating" value="1" class="h-4 w-4 accent-blue-600" required>
<span>1 — Poor</span>
</label>
<label class="flex items-center gap-2 text-gray-800">
<input type="radio" name="rating" value="2" class="h-4 w-4 accent-blue-600" required>
<span>2</span>
</label>
<label class="flex items-center gap-2 text-gray-800">
<input type="radio" name="rating" value="3" class="h-4 w-4 accent-blue-600" required>
<span>3</span>
</label>
<label class="flex items-center gap-2 text-gray-800">
<input type="radio" name="rating" value="4" class="h-4 w-4 accent-blue-600" required>
<span>4</span>
</label>
<label class="flex items-center gap-2 text-gray-800">
<input type="radio" name="rating" value="5" class="h-4 w-4 accent-blue-600" required>
<span>5 — Excellent</span>
</label>
</div>
</fieldset>
<div class="flex flex-col gap-1.5 sm:col-span-2">
<label for="fb-worked" class="text-sm font-semibold text-gray-900">
What worked well?
<span class="font-normal text-gray-500">(optional)</span>
</label>
<textarea
id="fb-worked"
name="worked"
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>
<div class="flex flex-col gap-1.5 sm:col-span-2">
<label for="fb-improve" class="text-sm font-semibold text-gray-900">
What should we improve?
<span class="font-normal text-gray-500">(optional)</span>
</label>
<textarea
id="fb-improve"
name="improve"
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>
<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
<span class="font-normal text-gray-500">(optional)</span>
</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"
autocomplete="email"
aria-describedby="fb-email-help"
>
<p id="fb-email-help" class="text-sm text-gray-500">Only if you would like a reply.</p>
</div>
<input type="hidden" name="page" value="/your-page">
<!-- 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 feedback">
<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"
>
Send feedback
</button>
</form>
About this template
Most feedback forms ask too much. This one asks for a rating and two sentences, which is what people will actually give you at the bottom of a page. The rating is a set of radio buttons rather than stars, so it works with a keyboard and a screen reader and sorts cleanly when you export it.
A hidden page field records where the feedback came from. Put the same form on several pages, change that one value on each, and every submission arrives already tagged — no separate form per page, and no guessing which article someone meant.
The email field is optional on purpose. Requiring it lowers the response rate; leaving it optional still lets the people who want a reply leave an address, and because it is named email, Auto Reply can thank them automatically on Starter and above.
Fields in this template
| Field | Name | Type | Required | Why it is there |
|---|---|---|---|---|
| How would you rate this page? | rating | Single choice | Yes | Required, and the only required field. A number you can sort and average in the CSV export. |
| What worked well? | worked | Long text | No | Optional. Asking what worked first makes the second question read as constructive rather than a complaint box. |
| What should we improve? | improve | Long text | No | Optional. The answer you will act on most often. |
email | No | Optional. Named email so Auto Reply finds it without configuration. | ||
| (hidden) | page | Hidden value | No | Hidden. Change the value on each page that carries the form. |
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
- Swap the 1–5 scale for three faces or a thumbs up/down by editing the options — the column stays called
rating. - Fill
pagefrom JavaScript (location.pathname) if the form lives in a shared footer. - Change the hidden
_subjectto include the product area, so feedback for different teams is easy to filter in an inbox.
Where the submissions go
- Every submission is stored in your dashboard and emailed to you, with the rating and both answers in the message.
- Post it to a team channel too: Slack, Discord or Telegram — one chat destination per form on the Free plan.
- Export everything to CSV from the dashboard to chart ratings over time, on every plan.
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
Survey form template
Four question types — a 0–10 score, single choice, multiple choice and an open answer — in one page.
Support ticket form template
Category, urgency, where it happened and how to reproduce it — the details a first reply usually has to ask for.
Newsletter signup form template
Email, first name, the topics they want, and consent that is a real choice.
All form templates
Every template, in HTML, React and Tailwind.
Put the feedback form live
Free forever: 500 submissions a month across 10 forms, no card.