iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Forms & @tailwindcss/forms

Tailwind’s form utilities + @tailwindcss/forms plugin normalise browser styles and let you compose accessible inputs. Standard pieces: input, label, error message, focus ring.

Inputs, labels, errors, plugin

EXAMPLE
<!-- npm i -D @tailwindcss/forms -->
<!-- module.exports = { plugins: [require('@tailwindcss/forms')] } -->

<!-- 1) Text input with label + error -->
<div class="space-y-1">
    <label for="email" class="block text-sm font-medium text-slate-700">
        Email
    </label>
    <input
        id="email"
        type="email"
        autocomplete="email"
        class="block w-full rounded-md border-slate-300 shadow-sm focus:border-sky-500 focus:ring-sky-500 sm:text-sm"
        aria-describedby="email-error"
        aria-invalid="true"
    />
    <p id="email-error" class="text-sm text-rose-600">
        Please enter a valid email
    </p>
</div>

<!-- 2) Password with show/hide -->
<div class="space-y-1">
    <label for="password" class="block text-sm font-medium">Password</label>
    <div class="relative">
        <input
            id="password"
            type="password"
            class="block w-full rounded-md border-slate-300 pr-10 focus:border-sky-500 focus:ring-sky-500"
        />
        <button type="button" class="absolute inset-y-0 right-0 px-3 text-slate-500">
            👁
        </button>
    </div>
</div>

<!-- 3) Textarea -->
<textarea
    rows="4"
    class="block w-full rounded-md border-slate-300 focus:border-sky-500 focus:ring-sky-500"
    placeholder="Your message…"
></textarea>

<!-- 4) Select -->
<select class="block w-full rounded-md border-slate-300 focus:border-sky-500 focus:ring-sky-500">
    <option>Sydney</option>
    <option>Melbourne</option>
    <option>Brisbane</option>
</select>

<!-- 5) Checkbox -->
<label class="flex items-start gap-2">
    <input
        type="checkbox"
        class="mt-1 h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500"
    />
    <span class="text-sm text-slate-700">
        I accept the <a href="/terms" class="text-sky-600 underline">terms</a>
    </span>
</label>

<!-- 6) Radio group -->
<fieldset class="space-y-2">
    <legend class="text-sm font-medium">Plan</legend>
    <label class="flex items-center gap-2">
        <input name="plan" type="radio" class="h-4 w-4 border-slate-300 text-sky-600 focus:ring-sky-500" />
        <span class="text-sm">Free</span>
    </label>
    <label class="flex items-center gap-2">
        <input name="plan" type="radio" class="h-4 w-4 border-slate-300 text-sky-600 focus:ring-sky-500" checked />
        <span class="text-sm">Pro</span>
    </label>
</fieldset>

<!-- 7) File upload (basic — drop-zone takes more) -->
<label class="flex items-center gap-2 cursor-pointer">
    <span class="px-3 py-1.5 bg-slate-100 rounded text-sm">Choose file</span>
    <input type="file" class="sr-only" />
    <span class="text-sm text-slate-500">No file chosen</span>
</label>

<!-- 8) Full form layout pattern -->
<form class="space-y-6 max-w-md">
    <div class="space-y-1">
        <label class="block text-sm font-medium">First name</label>
        <input class="block w-full rounded-md border-slate-300 focus:border-sky-500 focus:ring-sky-500" />
    </div>
    <div class="grid grid-cols-2 gap-4">
        <div class="space-y-1">
            <label class="block text-sm font-medium">City</label>
            <input class="block w-full rounded-md border-slate-300 focus:border-sky-500 focus:ring-sky-500" />
        </div>
        <div class="space-y-1">
            <label class="block text-sm font-medium">Postcode</label>
            <input class="block w-full rounded-md border-slate-300 focus:border-sky-500 focus:ring-sky-500" />
        </div>
    </div>

    <div class="flex justify-end gap-3 pt-4">
        <button type="button" class="px-4 py-2 text-sm rounded border border-slate-300">
            Cancel
        </button>
        <button type="submit" class="px-4 py-2 text-sm rounded bg-sky-600 text-white hover:bg-sky-700">
            Save
        </button>
    </div>
</form>

<!-- 9) Validation states (via aria + classes) -->
<input
    type="email"
    aria-invalid="true"
    class="border-rose-500 focus:border-rose-500 focus:ring-rose-500"
/>
<input
    type="email"
    aria-invalid="false"
    class="border-emerald-500 focus:border-emerald-500 focus:ring-emerald-500"
/>

<!-- Tailwind 3.0+ — :has() and :invalid -->
<input
    type="email"
    class="valid:border-emerald-500 invalid:border-rose-500 focus:ring-sky-500"
/>

<!-- 10) Disabled state -->
<input disabled class="bg-slate-100 cursor-not-allowed text-slate-500 border-slate-200" />
<button disabled class="bg-slate-300 text-slate-500 cursor-not-allowed px-4 py-2 rounded">
    Submit
</button>

<!-- 11) Required indicator -->
<label class="block text-sm font-medium">
    Email <span class="text-rose-500">*</span>
</label>

<!-- 12) Loading button -->
<button class="bg-sky-600 text-white px-4 py-2 rounded inline-flex items-center gap-2" disabled>
    <svg class="animate-spin h-4 w-4" viewBox="0 0 24 24">
        <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/>
        <path d="M4 12a8 8 0 018-8" stroke="currentColor" stroke-width="4" fill="none"/>
    </svg>
    Saving…
</button>

<!-- 13) Accessibility checklist -->
<!--   • Every input has a <label> (or aria-label) -->
<!--   • Group radio/checkbox with <fieldset> + <legend> -->
<!--   • Use aria-describedby for help / error text -->
<!--   • Use aria-invalid + visible color for errors -->
<!--   • Focus rings — never remove with outline-none unless you replaced them -->
<!--   • Contrast — test with WCAG tools -->
<!--   • Don't disable buttons silently; show loading state -->
<!--   • Use autocomplete attributes (email, current-password, name) -->

<!-- 14) When to reach for headless libraries -->
<!-- Tailwind's @tailwindcss/forms styles native inputs.
     For complex widgets (combobox, dropdown, modal), use Headless UI (@headlessui/react),
     Radix UI, or React Aria — they handle accessibility + keyboard. Style with Tailwind. -->

<!-- 15) Forms libraries that pair with Tailwind -->
<!--   • React Hook Form — form state -->
<!--   • Zod / Valibot — schema validation -->
<!--   • Headless UI / Radix — accessible primitives -->
<!--   • Conform — server + client validation for React + Remix / Next -->

Why it matters

Install @tailwindcss/forms first — native input styling becomes sane. Pair Tailwind for layout with Headless UI / Radix for complex widgets — you get accessibility AND visual control without reinventing dropdowns.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
<!-- @tailwindcss/forms resets form styles to be Tailwind-friendly -->
<input class="rounded border-slate-300 focus:ring-emerald-500">
Try it Yourself »

Discussion

Loading…