React Form with Tailwind Styled Form
Tailwind CSS excels at form styling because utility classes map directly to the states that matter — focus, disabled, hover, and error. The key is defining a consistent set of classes for each field state and reusing them across every input in the form, rather than applying classes ad hoc. This example shows focus rings, transition effects, a disabled submit button during fake submission, and dark-mode-ready classes.
The challenge
Tailwind utility classes can become repetitive and inconsistent in forms without a systematic approach to field styling.
- Keeping the focus ring consistent across all form elements without writing the same classes repeatedly
- Disabling the submit button during submission and reflecting that visually with opacity and cursor changes
- Making hover states on buttons feel responsive without janky transitions
- Getting dark mode to work on form inputs without the default browser dark mode overriding your styles
Working code example
Here is a complete, self-contained working example you can drop directly into any React project. It uses Tailwind CSS for styling and requires no external dependencies beyond React itself.
import { useState } from 'react';
const inputCls = 'w-full border border-gray-300 dark:border-slate-600 rounded-xl px-4 py-2.5 text-sm text-gray-900 dark:text-slate-100 bg-white dark:bg-slate-800 outline-none transition focus:border-teal-500 focus:ring-2 focus:ring-teal-500/30 placeholder:text-gray-400 dark:placeholder:text-slate-500';
const labelCls = 'block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5';
export default function TailwindForm() {
const [form, setForm] = useState({ name: '', email: '', message: '', agree: false });
const [submitting, setSubmitting] = useState(false);
const [done, setDone] = useState(false);
const handle = (e) => {
const { name, value, type, checked } = e.target;
setForm((f) => ({ ...f, [name]: type === 'checkbox' ? checked : value }));
};
const handleSubmit = (e) => {
e.preventDefault();
setSubmitting(true);
setTimeout(() => {
setSubmitting(false);
setDone(true);
}, 1800);
};
if (done)
return (
<div className="max-w-md mx-auto p-6 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-2xl text-green-700 dark:text-green-300">
<p className="font-semibold">Message sent!</p>
<p className="text-sm mt-1 opacity-80">We'll get back to you within 24 hours.</p>
</div>
);
return (
<form onSubmit={handleSubmit}
className="max-w-md mx-auto p-6 bg-white dark:bg-slate-900 rounded-2xl shadow-lg border border-gray-100 dark:border-slate-700 space-y-5">
<h2 className="text-xl font-bold text-gray-900 dark:text-slate-100">Get in touch</h2>
<div>
<label className={labelCls}>Your name</label>
<input name="name" value={form.name} onChange={handle}
placeholder="Jane Smith" className={inputCls} />
</div>
<div>
<label className={labelCls}>Email address</label>
<input name="email" type="email" value={form.email} onChange={handle}
placeholder="jane@example.com" className={inputCls} />
</div>
<div>
<label className={labelCls}>Message</label>
<textarea name="message" value={form.message} onChange={handle}
rows={4} style={{ resize: 'none' }} placeholder="How can we help?"
className={inputCls} />
</div>
<label className="flex items-start gap-3 cursor-pointer group">
<input name="agree" type="checkbox" checked={form.agree} onChange={handle}
className="w-4 h-4 mt-0.5 accent-teal-600 rounded" />
<span className="text-sm text-gray-600 dark:text-slate-400 group-hover:text-gray-800 dark:group-hover:text-slate-200 transition">
I agree to the privacy policy and terms of service
</span>
</label>
<button type="submit" disabled={submitting || !form.agree}
className="w-full bg-teal-600 hover:bg-teal-700 text-white font-semibold py-2.5 rounded-xl shadow transition disabled:opacity-50 disabled:cursor-not-allowed active:scale-[0.98]">
{submitting ? 'Sending…' : 'Send Message'}
</button>
</form>
);
}How ReactForm.co helps
ReactForm's visual builder handles all of the above — tailwind styled form configuration, validation rules, state management, and responsive layout — without writing a single line of code. Drag fields onto the canvas, configure their properties in the sidebar, and get production-ready React output. You can publish the form and collect responses instantly, or export the JSX to drop into your own codebase.
Build this form visuallyFrequently asked questions
How do I add a focus ring to form inputs with Tailwind?
Add focus:ring-2 focus:ring-teal-500 and outline-none to the input element. The outline-none removes the default browser focus outline and focus:ring-2 replaces it with a Tailwind ring. Add focus:ring-teal-500/30 for a soft semi-transparent ring color. This pattern works the same for inputs, textareas, and selects.
How do I disable a button while a form is submitting in React?
Keep a submitting boolean in state, set it to true when submit starts, and back to false when done. Pass it to the button: disabled={submitting}. Add disabled:opacity-50 disabled:cursor-not-allowed to the button's className so the disabled state is visually obvious. Change the button text to show progress: {submitting ? "Saving…" : "Save"}.
How do I make Tailwind form inputs work in dark mode?
Add dark: variants for background color (dark:bg-slate-800), border (dark:border-slate-600), and text (dark:text-slate-100). The default browser form styles for dark mode can look jarring — explicitly setting these classes ensures consistent appearance regardless of OS preference. Make sure your Tailwind config has darkMode: "class" or "media" set.
Related topics
Build this form visually — no code needed
ReactForm.co handles tailwind styled form fields, validation, conditional logic, and responsive layout automatically. Publish in minutes and collect responses for free.

