send.webforms.toRead the docs →

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.

Get an endpointRead the docs

Setup

  1. Copy your endpoint

    Create a form in the dashboard and paste the URL into the action attribute.

  2. 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.

contact-form.html
<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.

ContactForm.tsx
"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&apos;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.

Ready to wire up your Tailwind CSS form?

Free for 300 submissions a month. Endpoint in under a minute, no card required.

Start free