ReactForm
Home/Tools/Contact Form

React Form with Contact Form

A contact form is often the first form users encounter on a website, so it sets the tone for perceived quality. Using a green success banner instead of a browser alert keeps the experience smooth and on-brand. The subject dropdown — General, Support, Sales, Partnership — routes the message without requiring any extra explanation from the user.

ReactForm Team·May 2026·4 min read

The challenge

Contact forms seem simple but require solid validation and a clear, non-intrusive success state instead of browser alerts.

  • Showing a success message without unmounting the entire form (so users can see what they submitted)
  • Validating all four fields with clear per-field error messages without a library
  • The subject dropdown needs a default empty state that is prevented from being submitted
  • Making the form look complete and professional without excess styling code

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.

ContactForm.jsx
import { useState } from 'react';

const SUBJECTS = ['General', 'Support', 'Sales', 'Partnership'];

function validate(f) {
  const errs = {};
  if (!f.name.trim()) errs.name = 'Name is required';
  if (!f.email.trim()) errs.email = 'Email is required';
  else if (!/\S+@\S+\.\S+/.test(f.email)) errs.email = 'Enter a valid email';
  if (!f.subject) errs.subject = 'Please select a subject';
  if (!f.message.trim()) errs.message = 'Message is required';
  else if (f.message.trim().length < 10) errs.message = 'Message must be at least 10 characters';
  return errs;
}

export default function ContactForm() {
  const [form, setForm] = useState({ name: '', email: '', subject: '', message: '' });
  const [errors, setErrors] = useState({});
  const [success, setSuccess] = useState(false);

  const handle = (e) => {
    setForm({ ...form, [e.target.name]: e.target.value });
    if (errors[e.target.name]) setErrors({ ...errors, [e.target.name]: '' });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const errs = validate(form);
    if (Object.keys(errs).length) { setErrors(errs); return; }
    setSuccess(true);
  };

  const inputCls = (field) =>
    `w-full border rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-teal-500 ${errors[field] ? 'border-red-400' : 'border-gray-300'}`;

  return (
    <div className="max-w-md mx-auto p-6 bg-white rounded-2xl shadow space-y-4">
      <h2 className="text-xl font-bold text-gray-800">Contact Us</h2>

      {success && (
        <div className="bg-green-50 border border-green-200 text-green-700 rounded-xl px-4 py-3 text-sm font-medium">
          Message sent! We'll get back to you within 24 hours.
        </div>
      )}

      {!success && (
        <form onSubmit={handleSubmit} className="space-y-4">
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">Name</label>
            <input name="name" value={form.name} onChange={handle} className={inputCls('name')} />
            {errors.name && <p className="text-red-500 text-xs mt-1">{errors.name}</p>}
          </div>
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">Email</label>
            <input name="email" type="email" value={form.email} onChange={handle} className={inputCls('email')} />
            {errors.email && <p className="text-red-500 text-xs mt-1">{errors.email}</p>}
          </div>
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">Subject</label>
            <select name="subject" value={form.subject} onChange={handle} className={`${inputCls('subject')} bg-white`}>
              <option value="">Select a subject…</option>
              {SUBJECTS.map((s) => <option key={s} value={s}>{s}</option>)}
            </select>
            {errors.subject && <p className="text-red-500 text-xs mt-1">{errors.subject}</p>}
          </div>
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">Message</label>
            <textarea name="message" value={form.message} onChange={handle} rows={4}
              style={{ resize: 'none' }} className={inputCls('message')} />
            {errors.message && <p className="text-red-500 text-xs mt-1">{errors.message}</p>}
          </div>
          <button type="submit"
            className="w-full bg-teal-600 text-white font-semibold py-2 rounded-lg hover:bg-teal-700 transition">
            Send Message
          </button>
        </form>
      )}
    </div>
  );
}

How ReactForm.co helps

ReactForm's visual builder handles all of the above — contact 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 visually

Frequently asked questions

How do I show a success banner instead of an alert after form submission?

Keep a success boolean in state, set it to true on successful submission, and render the banner conditionally with {success && <Banner />}. Instead of hiding the whole form immediately, you can show the banner above the form or replace just the submit button area. This gives users a visible confirmation without a jarring browser dialog.

How do I clear individual field errors when the user starts correcting them?

In your onChange handler, check if the field currently has an error and clear it: if (errors[e.target.name]) setErrors({ ...errors, [e.target.name]: "" }). This gives immediate positive feedback — the red border disappears as soon as the user begins fixing the issue, without waiting for them to submit again.

How do I actually send the contact form data somewhere?

Replace the setSuccess(true) call with a fetch or axios POST to your backend endpoint: await fetch("/api/contact", { method: "POST", body: JSON.stringify(form), headers: { "Content-Type": "application/json" } }). Handle errors with a try/catch. For serverless options, services like Formspree, Resend, or EmailJS provide ready-made endpoints.

Related topics

Build this form visually — no code needed

ReactForm.co handles contact form fields, validation, conditional logic, and responsive layout automatically. Publish in minutes and collect responses for free.