Tailwind CSS Contact Form — Beautiful & Functional
A complete contact form built with Tailwind CSS. Includes focused states, validation styling, loading states, and a working backend via Formboost. Copy-paste ready.
Tailwind CSS Contact Form — Beautiful & Functional
Tailwind makes it easy to build a polished contact form without writing a single line of custom CSS. This guide gives you a complete, styled contact form — inputs, labels, focus rings, error states, and a loading button — plus a working form backend.
The Complete Form (HTML + Tailwind)
Drop this into any page that has Tailwind loaded:
1<div class="min-h-screen bg-gray-50 flex items-center justify-center p-4">
2 <div class="bg-white border border-gray-200 rounded-2xl shadow-sm p-8 w-full max-w-md">
3 <h2 class="text-xl font-semibold text-gray-900 mb-6">Get in Touch</h2>
4
5 <form action="https://formboost.app/f/YOUR_ENDPOINT_ID" method="POST" class="space-y-5">
6 <!-- Honeypot for bots -->
7 <input type="text" name="_honey" class="hidden" tabindex="-1" autocomplete="off" />
8
9 <!-- Name -->
10 <div class="flex flex-col gap-1.5">
11 <label for="name" class="text-sm font-medium text-gray-700">Name</label>
12 <input
13 id="name"
14 type="text"
15 name="name"
16 placeholder="Jane Smith"
17 required
18 class="w-full rounded-lg border border-gray-300 px-3.5 py-2.5 text-sm text-gray-900 placeholder-gray-400 outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20"
19 />
20 </div>
21
22 <!-- Email -->
23 <div class="flex flex-col gap-1.5">
24 <label for="email" class="text-sm font-medium text-gray-700">Email</label>
25 <input
26 id="email"
27 type="email"
28 name="email"
29 placeholder="[email protected]"
30 required
31 class="w-full rounded-lg border border-gray-300 px-3.5 py-2.5 text-sm text-gray-900 placeholder-gray-400 outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20"
32 />
33 </div>
34
35 <!-- Message -->
36 <div class="flex flex-col gap-1.5">
37 <label for="message" class="text-sm font-medium text-gray-700">Message</label>
38 <textarea
39 id="message"
40 name="message"
41 rows="5"
42 placeholder="What can we help you with?"
43 class="w-full resize-y rounded-lg border border-gray-300 px-3.5 py-2.5 text-sm text-gray-900 placeholder-gray-400 outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20"
44 ></textarea>
45 </div>
46
47 <button
48 type="submit"
49 class="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-60"
50 >
51 Send Message
52 </button>
53 </form>
54 </div>
55</div>React + Tailwind Version
The same form as a React component with loading and success states:
1import { useState } from "react";
2
3export function ContactForm() {
4 const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
5
6 async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
7 e.preventDefault();
8 setStatus("sending");
9
10 const data = Object.fromEntries(new FormData(e.currentTarget));
11
12 try {
13 const res = await fetch("https://formboost.app/f/YOUR_ENDPOINT_ID", {
14 method: "POST",
15 headers: { "Content-Type": "application/json" },
16 body: JSON.stringify(data),
17 });
18
19 if (!res.ok) throw new Error();
20 setStatus("sent");
21 } catch {
22 setStatus("error");
23 }
24 }
25
26 if (status === "sent") {
27 return (
28 <div className="rounded-2xl border border-green-200 bg-green-50 p-8 text-center">
29 <p className="text-lg font-semibold text-green-800">Message sent!</p>
30 <p className="mt-1 text-sm text-green-600">We'll get back to you soon.</p>
31 </div>
32 );
33 }
34
35 return (
36 <form onSubmit={handleSubmit} className="space-y-5">
37 <div className="flex flex-col gap-1.5">
38 <label htmlFor="name" className="text-sm font-medium text-gray-700">Name</label>
39 <input
40 id="name"
41 name="name"
42 type="text"
43 required
44 placeholder="Jane Smith"
45 className="w-full rounded-lg border border-gray-300 px-3.5 py-2.5 text-sm outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20"
46 />
47 </div>
48
49 <div className="flex flex-col gap-1.5">
50 <label htmlFor="email" className="text-sm font-medium text-gray-700">Email</label>
51 <input
52 id="email"
53 name="email"
54 type="email"
55 required
56 placeholder="[email protected]"
57 className="w-full rounded-lg border border-gray-300 px-3.5 py-2.5 text-sm outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20"
58 />
59 </div>
60
61 <div className="flex flex-col gap-1.5">
62 <label htmlFor="message" className="text-sm font-medium text-gray-700">Message</label>
63 <textarea
64 id="message"
65 name="message"
66 rows={5}
67 placeholder="Your message..."
68 className="w-full resize-y rounded-lg border border-gray-300 px-3.5 py-2.5 text-sm outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20"
69 />
70 </div>
71
72 {status === "error" && (
73 <p className="text-sm text-red-600">Something went wrong. Please try again.</p>
74 )}
75
76 <button
77 type="submit"
78 disabled={status === "sending"}
79 className="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-60"
80 >
81 {status === "sending" ? "Sending…" : "Send Message"}
82 </button>
83 </form>
84 );
85}Tailwind Classes Explained
| Class | What it does |
|---|---|
rounded-lg | 8px border radius on inputs |
border border-gray-300 | Subtle 1px border |
px-3.5 py-2.5 | Comfortable padding (14px / 10px) |
focus:border-blue-500 | Blue border on focus |
focus:ring-2 focus:ring-blue-500/20 | Soft blue glow around focused input |
transition | Smooth border/ring animation |
disabled:opacity-60 | Dim button while submitting |
resize-y | Allow textarea to resize vertically only |
Adding Validation Error Styling
Show a red border when a field has a validation error:
1// With react-hook-form
2import { useForm } from "react-hook-form";
3
4const inputClass = (hasError: boolean) =>
5 `w-full rounded-lg border px-3.5 py-2.5 text-sm outline-none transition focus:ring-2 ${
6 hasError
7 ? "border-red-400 focus:border-red-500 focus:ring-red-500/20"
8 : "border-gray-300 focus:border-blue-500 focus:ring-blue-500/20"
9 }`;
10
11// In the form:
12<input
13 {...register("email", { required: true })}
14 className={inputClass(!!errors.email)}
15/>
16{errors.email && <span className="text-xs text-red-500">Email is required</span>}Dark Mode Variant
Add dark: variants for dark mode support:
1<input
2 class="
3 w-full rounded-lg border px-3.5 py-2.5 text-sm outline-none transition
4 border-gray-300 bg-white text-gray-900 placeholder-gray-400
5 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20
6 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-500
7 dark:focus:border-blue-400 dark:focus:ring-blue-400/20
8 "
9/>Form Card Layout
Wrap the form in a card for a clean, centered layout:
1<div class="min-h-screen bg-gray-50 dark:bg-gray-950 flex items-center justify-center p-4">
2 <div class="
3 bg-white dark:bg-gray-900
4 border border-gray-200 dark:border-gray-800
5 rounded-2xl shadow-sm
6 p-8 w-full max-w-md
7 ">
8 <h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-1">Contact us</h2>
9 <p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
10 We'll get back to you within 24 hours.
11 </p>
12 <!-- form fields here -->
13 </div>
14</div>Get Your Backend
The form UI is yours — Tailwind handles the design. Formboost handles the backend: receiving submissions, sending you email notifications, and filtering spam.
- Sign up free — no credit card
- Create an endpoint (under 60 seconds)
- Replace
YOUR_ENDPOINT_IDin theactionURL above