Customer Feedback & CSAT Form
A modern customer feedback and satisfaction survey form styled with Tailwind CSS. Features interactive 1-5 star ratings, feedback categories (Experience, Feature Request, Bug Report), invisible honeypot anti-spam defense, and instant alerts dispatched to your product team in Slack, Discord, or Google Sheets.
Customer Feedback
Collect customer satisfaction ratings, feedback categories, and open-ended commentary.
style="color:#9ece6a;">"use client";
import { useState } from style="color:#9ece6a;">"react";
import { Star, MessageSquare } from style="color:#9ece6a;">"lucide-react";
export default function CustomerFeedbackForm() {
const [rating, setRating] = useState<style="color:#f7768e;font-weight:600;">number>(5);
const [hoverRating, setHoverRating] = useState<style="color:#f7768e;font-weight:600;">number | null>(null);
const [category, setCategory] = useState<style="color:#f7768e;font-weight:600;">string>(style="color:#9ece6a;">"Experience");
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 categories = [style="color:#9ece6a;">"Experience", style="color:#9ece6a;">"Feature Request", style="color:#9ece6a;">"Bug Report"];
const activeRating = hoverRating !== null ? hoverRating : rating;
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);
style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">// Explicitly append the interactive rating and category states
formData.set(style="color:#9ece6a;">"rating", rating.toString());
formData.set(style="color:#9ece6a;">"category", category);
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");
form.reset();
} else {
const data = await response.json().catch(() => null);
setErrorMessage(data?.error || style="color:#9ece6a;">"Feedback 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-[#14151b] border border-zinc-800 rounded-2xl p-6 sm:p-7 shadow-2xl text-zinc-100">
<style="color:#f7768e;font-weight:600;">h2 className=style="color:#9ece6a;">"text-2xl font-bold text-white mb-1">Customer Feedback</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">How would you rate your experience with us?</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-emerald-950/80 border border-emerald-500/40 text-emerald-300 rounded-xl text-center space-y-2">
<style="color:#f7768e;font-weight:600;">p className=style="color:#9ece6a;">"font-bold text-base">Thank you for your feedback!</style="color:#f7768e;font-weight:600;">p>
<style="color:#f7768e;font-weight:600;">p className=style="color:#9ece6a;">"text-xs text-emerald-400">
Your response has been recorded. Our team reviews customer feedback regularly to improve the product.
</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-emerald-600 hover:bg-emerald-500 text-white rounded-lg text-xs font-semibold transition cursor-pointer"
>
Submit More Feedback
</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 (Catches automated bots) */}
<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;">/* Interactive Star Rating */}
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"text-center bg-zinc-900/60 border border-zinc-800/80 rounded-xl p-3.5">
<style="color:#f7768e;font-weight:600;">label className=style="color:#9ece6a;">"block text-xs font-medium text-zinc-300 mb-2">
Overall Satisfaction Score
</style="color:#f7768e;font-weight:600;">label>
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"flex items-center justify-center gap-2">
{[1, 2, 3, 4, 5].map((star) => (
<style="color:#f7768e;font-weight:600;">button
key={star}
type=style="color:#9ece6a;">"button"
onClick={() => setRating(star)}
onMouseEnter={() => setHoverRating(star)}
onMouseLeave={() => setHoverRating(null)}
className=style="color:#9ece6a;">"p-1 rounded-lg hover:bg-zinc-800 transition-colors cursor-pointer"
>
<style="color:#f7768e;font-weight:600;">Star
className={`size-6 transition-all ${
star <= activeRating
? style="color:#9ece6a;">"text-amber-400 fill-amber-400 scale-110 drop-shadow-[0_0_8px_rgba(251,191,36,0.35)]"
: style="color:#9ece6a;">"text-zinc-600"
}`}
/>
</style="color:#f7768e;font-weight:600;">button>
))}
</style="color:#f7768e;font-weight:600;">div>
<style="color:#f7768e;font-weight:600;">span className=style="color:#9ece6a;">"text-[11px] font-mono text-amber-400 font-medium mt-1.5 inline-block">
{activeRating} / 5 Stars
</style="color:#f7768e;font-weight:600;">span>
</style="color:#f7768e;font-weight:600;">div>
{style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">/* Category Selector */}
<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-400 mb-1.5">
Topic
</style="color:#f7768e;font-weight:600;">label>
<style="color:#f7768e;font-weight:600;">div className=style="color:#9ece6a;">"grid grid-cols-3 gap-2">
{categories.map((cat) => (
<style="color:#f7768e;font-weight:600;">button
key={cat}
type=style="color:#9ece6a;">"button"
onClick={() => setCategory(cat)}
className={`py-2 px-2 text-xs font-mono rounded-lg transition-all text-center cursor-pointer ${
category === cat
? style="color:#9ece6a;">"bg-amber-400 text-zinc-950 font-bold"
: style="color:#9ece6a;">"bg-zinc-900 text-zinc-400 border border-zinc-800 hover:text-white"
}`}
>
{cat}
</style="color:#f7768e;font-weight:600;">button>
))}
</style="color:#f7768e;font-weight:600;">div>
</style="color:#f7768e;font-weight:600;">div>
{style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">/* Email (Optional) */}
<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-400 mb-1" htmlFor=style="color:#9ece6a;">"feedback-email">
Your Email <style="color:#f7768e;font-weight:600;">span className=style="color:#9ece6a;">"text-zinc-500">(Optional for replies)</style="color:#f7768e;font-weight:600;">span>
</style="color:#f7768e;font-weight:600;">label>
<style="color:#f7768e;font-weight:600;">input
id=style="color:#9ece6a;">"feedback-email"
name=style="color:#9ece6a;">"email"
type=style="color:#9ece6a;">"email"
placeholder=style="color:#9ece6a;">"you@company.com"
className=style="color:#9ece6a;">"w-full bg-zinc-900 text-white border border-zinc-800 rounded-lg px-3.5 py-2 text-sm placeholder:text-zinc-500 focus:outline-none focus:border-amber-400 transition"
/>
</style="color:#f7768e;font-weight:600;">div>
{style=style="color:#9ece6a;">"color:#565f89;font-style:italic;">/* Comments Textarea */}
<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-400 mb-1" htmlFor=style="color:#9ece6a;">"feedback-comments">
Your Comments & Suggestions
</style="color:#f7768e;font-weight:600;">label>
<style="color:#f7768e;font-weight:600;">textarea
id=style="color:#9ece6a;">"feedback-comments"
name=style="color:#9ece6a;">"comments"
rows={3}
required
placeholder=style="color:#9ece6a;">"Tell us what you like or how we can improve..."
className=style="color:#9ece6a;">"w-full bg-zinc-900 text-white border border-zinc-800 rounded-lg px-3.5 py-2 text-sm placeholder:text-zinc-500 focus:outline-none focus:border-amber-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 bg-amber-400 hover:bg-amber-300 text-zinc-950 font-bold py-2.5 px-4 rounded-xl transition cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
<style="color:#f7768e;font-weight:600;">MessageSquare className=style="color:#9ece6a;">"size-4" />
<style="color:#f7768e;font-weight:600;">span>{status === style="color:#9ece6a;">"loading" ? style="color:#9ece6a;">"Submitting Feedback..." : style="color:#9ece6a;">"Submit Feedback"}</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
How are star ratings stored in Backforms?
Backforms saves the rating value (e.g. rating: 5) as a structured field in your form submission. You can filter, export to CSV, or auto-calculate average CSAT scores in Google Sheets.
Can I connect customer feedback directly to our team Slack channel?
Yes! Backforms provides a native Slack webhook integration. Whenever a user submits feedback or reports a bug, your product team will receive an instant Slack card with the rating, topic, and comments.
Can this form be embedded into an existing web app modal or footer?
Absolutely. Both the React / Next.js component and the HTML markup are self-contained and responsive, making them drop-in ready for modals, slide-overs, or footer sections.