CSS frameworks
A contact form styled with Tailwind CSS
Tailwind gives you the styling; this gives you the part that actually receives the data. Below is a full contact form with proper labels, visible focus rings, disabled and error states, and a real endpoint behind it. It is deliberately plain Tailwind with no plugin dependencies, so it drops into any project without touching your config.
Setup
Copy your endpoint
Create a form in the dashboard and paste the URL into the action attribute.
Paste the markup
No @tailwindcss/forms plugin required - every input is styled explicitly so it looks the same with or without a preflight reset.
Tailwind CSS examples
Swap in your own endpoint and these run as-is.
Works as-is with no JavaScript. Add the fetch handler from the JavaScript guide if you want an inline success state.
<form action="https://send.webforms.to/wft_your_key" method="POST" class="mx-auto w-full max-w-md space-y-5">
<div>
<label for="name" class="block text-sm font-medium text-gray-900">
Name
</label>
<input
id="name"
name="name"
type="text"
required
class="mt-1.5 block w-full rounded-lg border border-gray-300 px-3 py-2
text-sm text-gray-900 shadow-sm outline-none transition
placeholder:text-gray-400
focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30"
placeholder="Ada Lovelace"
/>
</div>
<div>
<label for="email" class="block text-sm font-medium text-gray-900">
Email
</label>
<input
id="email"
name="email"
type="email"
required
class="mt-1.5 block w-full rounded-lg border border-gray-300 px-3 py-2
text-sm text-gray-900 shadow-sm outline-none transition
placeholder:text-gray-400
focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30"
placeholder="you@example.com"
/>
</div>
<div>
<label for="message" class="block text-sm font-medium text-gray-900">
Message
</label>
<textarea
id="message"
name="message"
rows="4"
required
class="mt-1.5 block w-full resize-y rounded-lg border border-gray-300 px-3 py-2
text-sm text-gray-900 shadow-sm outline-none transition
placeholder:text-gray-400
focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30"
placeholder="How can we help?"
></textarea>
</div>
<input type="hidden" name="_next" value="https://example.com/thanks" />
<input
name="_gotcha"
tabindex="-1"
aria-hidden="true"
class="absolute -left-[9999px]"
autocomplete="off"
/>
<button
type="submit"
class="inline-flex w-full items-center justify-center rounded-lg bg-indigo-600
px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition
hover:bg-indigo-500
focus-visible:outline focus-visible:outline-2
focus-visible:outline-offset-2 focus-visible:outline-indigo-600
disabled:cursor-not-allowed disabled:opacity-60"
>
Send message
</button>
</form>Dark-mode React version with an error state and a disabled submit while sending.
"use client";
import { useState } from "react";
const field =
"mt-1.5 block w-full rounded-lg border border-white/15 bg-white/5 px-3 py-2 " +
"text-sm text-white outline-none transition placeholder:text-white/35 " +
"focus:border-indigo-400 focus:ring-2 focus:ring-indigo-400/30";
export function ContactForm() {
const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("sending");
const res = await fetch("https://send.webforms.to/wft_your_key", {
method: "POST",
headers: { accept: "application/json" },
body: new FormData(e.currentTarget),
});
setStatus(res.ok ? "sent" : "error");
}
if (status === "sent") {
return (
<p role="status" className="rounded-lg bg-emerald-500/10 px-4 py-3 text-sm text-emerald-300">
Thanks - we'll be in touch.
</p>
);
}
return (
<form onSubmit={onSubmit} className="w-full max-w-md space-y-5">
<div>
<label htmlFor="name" className="block text-sm font-medium text-white/80">Name</label>
<input id="name" name="name" required className={field} />
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-white/80">Email</label>
<input id="email" name="email" type="email" required className={field} />
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-white/80">Message</label>
<textarea id="message" name="message" rows={4} required className={field} />
</div>
<button
disabled={status === "sending"}
className="w-full rounded-lg bg-indigo-500 px-4 py-2.5 text-sm font-semibold
text-white transition hover:bg-indigo-400 disabled:opacity-60"
>
{status === "sending" ? "Sending…" : "Send message"}
</button>
{status === "error" && (
<p role="alert" className="text-sm text-red-400">Something went wrong. Try again?</p>
)}
</form>
);
}Tailwind CSS form FAQ
No. Every input in these examples is styled explicitly, so they render the same whether or not the plugin is installed.
Yes. The utilities used here are stable across v3 and v4 - there is no config or plugin dependency.
Related guides
Ready to wire up your Tailwind CSS form?
Free for 300 submissions a month. Endpoint in under a minute, no card required.
Start free