How to Build a Contact Form with Tailwind CSS
Last updated: March 2026
Tailwind CSS makes it easy to build clean, responsive forms with utility classes. But styling is only half the problem: you still need a backend to process submissions. This guide shows how to build a polished Tailwind CSS contact form and connect it to FormWit so it actually sends emails.
No server code, no API routes, no database setup. Just a beautiful form that works.
What you'll build
By the end of this guide you'll have:
- A responsive contact form styled entirely with Tailwind CSS utility classes
- Proper label/input styling with focus states and smooth transitions
- A dropdown select field for categorizing inquiries
- Inline validation feedback using Tailwind's peer classes
- A honeypot field for spam protection
- A working backend: when someone submits the form, you receive an email notification
The form connects to FormWit's serverless form backend. No server-side code required.
Build the Tailwind contact form
Step 1: Create a FormWit account and get your endpoint
Go to app.formwit.com/auth/signup and create a free account. No credit card required.
In your dashboard, click Create Form and give it a name (e.g., "Contact Form"). You'll get a unique endpoint URL that looks like this:
https://app.formwit.com/api/s/YOUR_FORM_ID Copy that URL. You'll use it in the next step.
Step 2: Create the form HTML with Tailwind classes
Paste this into your HTML file. Replace YOUR_FORM_ID with the endpoint URL from your FormWit dashboard.
<form action="https://app.formwit.com/api/s/YOUR_FORM_ID" method="POST"
class="max-w-lg mx-auto space-y-6 p-8">
<h2 class="text-2xl font-bold text-gray-900">Contact Us</h2>
<p class="text-gray-600">Fill out the form below and we'll get back to you.</p>
<div>
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">Name</label>
<input type="text" id="name" name="name" required
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"
placeholder="Your name" />
</div>
<div>
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" id="email" name="email" required
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"
placeholder="you@example.com" />
</div>
<div>
<label for="subject" class="block text-sm font-medium text-gray-700 mb-1">Subject</label>
<select id="subject" name="subject" required
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition">
<option value="">Select a topic...</option>
<option value="General">General Inquiry</option>
<option value="Support">Support</option>
<option value="Sales">Sales</option>
</select>
</div>
<div>
<label for="message" class="block text-sm font-medium text-gray-700 mb-1">Message</label>
<textarea id="message" name="message" rows="5" required
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition resize-y"
placeholder="Your message..."></textarea>
</div>
<!-- Honeypot spam protection -->
<input type="text" name="_gotcha" class="hidden" tabindex="-1" autocomplete="off" />
<button type="submit"
class="w-full py-3 px-6 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold
rounded-lg shadow-sm transition focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Send Message
</button>
</form> Here's what each Tailwind class is doing:
max-w-lg mx-auto- centers the form with a max width of 32remspace-y-6- adds consistent vertical spacing between fieldsfocus:ring-2 focus:ring-indigo-500- adds a visible focus ring for accessibilityrounded-lg shadow-sm- subtle rounded corners and shadow on inputstransition- smooth animation on hover and focus stateshiddenon the honeypot - hides the spam trap from real users while bots fill it in
Step 3: Add JavaScript for AJAX submission (optional)
By default, the form does a full page redirect after submission. If you want to keep the user on the same page, add this script:
const form = document.querySelector('form');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const button = form.querySelector('button[type="submit"]');
button.textContent = 'Sending...';
button.disabled = true;
const response = await fetch(form.action, {
method: 'POST',
body: new FormData(form),
});
if (response.ok) {
form.innerHTML = '<p class="text-center text-green-600 font-semibold py-8">Message sent! We\'ll be in touch.</p>';
} else {
button.textContent = 'Send Message';
button.disabled = false;
alert('Something went wrong. Please try again.');
}
}); This disables the button while sending and replaces the form with a success message on completion.
Step 4: Test it
Open your page in a browser, fill out the form, and submit. You'll see the submission appear in your FormWit dashboard and receive an email notification within seconds.
Dark mode variant
If your site supports dark mode, Tailwind's dark: prefix makes it straightforward to add a dark variant. Here's the same form adapted for dark backgrounds:
<form action="https://app.formwit.com/api/s/YOUR_FORM_ID" method="POST"
class="max-w-lg mx-auto space-y-6 p-8 bg-gray-900 rounded-2xl">
<h2 class="text-2xl font-bold text-white">Contact Us</h2>
<p class="text-gray-400">Fill out the form below and we'll get back to you.</p>
<div>
<label for="name" class="block text-sm font-medium text-gray-300 mb-1">Name</label>
<input type="text" id="name" name="name" required
class="w-full px-4 py-2.5 bg-gray-800 border border-gray-700 text-white rounded-lg
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition
placeholder-gray-500"
placeholder="Your name" />
</div>
<div>
<label for="email" class="block text-sm font-medium text-gray-300 mb-1">Email</label>
<input type="email" id="email" name="email" required
class="w-full px-4 py-2.5 bg-gray-800 border border-gray-700 text-white rounded-lg
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition
placeholder-gray-500"
placeholder="you@example.com" />
</div>
<div>
<label for="message" class="block text-sm font-medium text-gray-300 mb-1">Message</label>
<textarea id="message" name="message" rows="5" required
class="w-full px-4 py-2.5 bg-gray-800 border border-gray-700 text-white rounded-lg
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition resize-y
placeholder-gray-500"
placeholder="Your message..."></textarea>
</div>
<input type="text" name="_gotcha" class="hidden" tabindex="-1" autocomplete="off" />
<button type="submit"
class="w-full py-3 px-6 bg-indigo-600 hover:bg-indigo-500 text-white font-semibold
rounded-lg transition focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900
focus:ring-indigo-500">
Send Message
</button>
</form> Key changes: bg-gray-900 on the form wrapper, bg-gray-800 and border-gray-700 on inputs, text-white and text-gray-300 for text, and placeholder-gray-500 for muted placeholder text. The focus:ring-offset-gray-900 on the button ensures the focus ring looks right against the dark background.
Making it responsive
The form is already responsive out of the box. max-w-lg constrains the width on large screens, while w-full on every input ensures fields stretch to fill the container on small screens. No media queries needed.
If you want a two-column layout on larger screens (for example, name and email side by side), wrap those fields in a grid:
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div>
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">Name</label>
<input type="text" id="name" name="name" required
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"
placeholder="Your name" />
</div>
<div>
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" id="email" name="email" required
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"
placeholder="you@example.com" />
</div>
</div> On screens smaller than 640px (sm: breakpoint), the grid stacks to a single column. On larger screens, the two fields sit side by side. The rest of the form remains full-width below.
Tailwind form validation styles
HTML5 validation handles the logic (required fields, email format, etc.), but the default browser validation popups look inconsistent across browsers. Tailwind's peer utility lets you show inline validation messages with pure CSS. No JavaScript required.
Here's how it works:
<div>
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" id="email" name="email" required
class="peer w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"
placeholder="you@example.com" />
<p class="hidden peer-invalid:block text-sm text-red-500 mt-1">
Please enter a valid email address.
</p>
</div> The peer class on the input creates a relationship with the sibling <p> element. The validation message is hidden by default (hidden) and only appears when the input is in an invalid state (peer-invalid:block). This works for any HTML5 validation: required, type="email", minlength, pattern, and more.
You can also style the input border on invalid state:
<input type="email" name="email" required
class="peer w-full px-4 py-2.5 border border-gray-300 rounded-lg
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500
invalid:border-red-500 invalid:focus:ring-red-500 transition" /> This turns the border and focus ring red when the field is invalid, giving users immediate visual feedback.
Summary
Tailwind CSS gives you full control over form styling without writing custom CSS. Combined with a serverless form backend like FormWit, you get a professional contact form that looks great and actually works. No backend code, no server setup, no email configuration.
Everything in this guide is copy-paste ready. Grab the code, swap in your FormWit endpoint URL, and you have a production-ready contact form.
FormWit's free plan includes unlimited forms, 100 submissions per month, email notifications, and built-in spam protection. Get started free.
Related guides: HTML contact form · Simple contact form · Embed a contact form · Spam protection · Form templates
Frequently asked questions
Can I style FormWit forms with Tailwind?
Yes. FormWit forms are standard HTML, so Tailwind utility classes work directly on all form elements. There is no FormWit-specific markup or CSS to override. Style inputs, labels, buttons, and the form wrapper with any Tailwind classes you want.
What Tailwind classes work best for forms?
Key classes: w-full for full-width inputs, px-4 py-2.5 for comfortable padding, border border-gray-300 rounded-lg for borders, focus:ring-2 focus:ring-indigo-500 for accessible focus states, and space-y-6 on the form for consistent vertical spacing between fields.
Does Tailwind affect form submission?
No. Tailwind is a CSS utility framework that only affects visual styling. It has zero impact on form submission behavior. The form submits the same data to FormWit regardless of what CSS classes you use. Styling and functionality are completely independent.
Want to skip the setup?
FormWit gives you a form endpoint in 60 seconds. Free plan, no credit card.
Need a form fast?
Build one visually with our free HTML form generator — no coding required.
Try the Form Generator →Style your FormWit form with Tailwind
Add a contact form to your site in 30 seconds. No backend code required.
Try FormWit Free