Back to blog

    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

    ClassWhat it does
    rounded-lg8px border radius on inputs
    border border-gray-300Subtle 1px border
    px-3.5 py-2.5Comfortable padding (14px / 10px)
    focus:border-blue-500Blue border on focus
    focus:ring-2 focus:ring-blue-500/20Soft blue glow around focused input
    transitionSmooth border/ring animation
    disabled:opacity-60Dim button while submitting
    resize-yAllow 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.

    1. Sign up free — no credit card
    2. Create an endpoint (under 60 seconds)
    3. Replace YOUR_ENDPOINT_ID in the action URL above

    Get your free Formboost endpoint →