Forms

Inputs, validation styles, rings, and accessible focus states.

On this page

Forms Are Where UIs Break

Forms are the highest-risk UI area in production: they must be readable, accessible, consistent, and handle real-world states (focus, disabled, error, success). Tailwind makes forms clean when you follow a small set of patterns and do not improvise per page.

Input baseline (recommended default)

A good baseline uses a neutral border, comfortable padding, and a visible focus ring. This avoids layout shift and improves accessibility.

<label class="block">
  <span class="text-sm font-medium text-slate-900">Email</span>
  <input
    type="email"
    class="mt-2 w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm
           placeholder:text-slate-400
           focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
    placeholder="you@example.com"
  />
</label>

Labels, help text, and spacing

Production forms need consistent vertical rhythm. Use space-y utilities to avoid random margins across fields.

<form class="space-y-6">
  <div>
    <label class="block text-sm font-medium text-slate-900">Name</label>
    <input class="mt-2 w-full rounded-md border border-slate-300 px-3 py-2 text-sm
                 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
    <p class="mt-2 text-sm text-slate-600">This will appear on your profile.</p>
  </div>

  <div>
    <label class="block text-sm font-medium text-slate-900">Company</label>
    <input class="mt-2 w-full rounded-md border border-slate-300 px-3 py-2 text-sm
                 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
  </div>
</form>

Textarea pattern

Textareas should match inputs in border, radius, and focus behavior. Set a minimum height and allow resizing only if you want it.

<label class="block">
  <span class="text-sm font-medium text-slate-900">Message</span>
  <textarea
    class="mt-2 w-full min-h-[120px] rounded-md border border-slate-300 px-3 py-2 text-sm
           focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
  ></textarea>
</label>

Select pattern

Select elements are tricky across browsers. Keep them simple and consistent. Consider a forms plugin if you want perfect cross-browser styling.

<label class="block">
  <span class="text-sm font-medium text-slate-900">Plan</span>
  <select
    class="mt-2 w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm
           focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
    <option>Free</option>
    <option>Pro</option>
  </select>
</label>

Checkbox and radio

Use a clear click target and align label text properly. Keep spacing consistent so forms look professional.

<label class="flex items-start gap-3">
  <input type="checkbox" class="mt-1 h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500" />
  <span class="text-sm text-slate-700">
    I agree to the terms and conditions
  </span>
</label>

Error state (validation)

Error states should be obvious and consistent: change border, show helper text, and optionally add an icon. Do not rely only on color; include text feedback.

<div>
  <label class="block text-sm font-medium text-slate-900">Email</label>
  <input
    class="mt-2 w-full rounded-md border border-red-500 px-3 py-2 text-sm
           focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-red-500"
    value="wrong@"
  />
  <p class="mt-2 text-sm text-red-600">Please enter a valid email address.</p>
</div>

Success state (optional)

Success states should be subtle. Overusing success styling makes forms noisy. Use it when confirmation is important.

<div>
  <label class="block text-sm font-medium text-slate-900">Username</label>
  <input
    class="mt-2 w-full rounded-md border border-green-500 px-3 py-2 text-sm
           focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500"
    value="ozan"
  />
  <p class="mt-2 text-sm text-green-600">Looks good.</p>
</div>

Disabled and read-only fields

Disabled fields should look disabled and be clearly non-interactive. Read-only fields should be readable but not editable.

<input
  class="w-full rounded-md border border-slate-300 px-3 py-2 text-sm
         bg-slate-100 text-slate-600 cursor-not-allowed"
  disabled
  value="Disabled"
/>

Dark mode forms

In dark mode, inputs need different surface and border colors. Keep placeholders and helper text readable.

<input
  class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm
         focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
         dark:bg-slate-900 dark:border-slate-700 dark:text-slate-100 dark:placeholder:text-slate-500"
/>

Common mistakes

  • Removing focus outlines without adding focus rings.
  • Inconsistent spacing between fields (random mt values).
  • Error states without clear text explanation.
  • Placeholder used as a label (bad for accessibility).
  • Forms that break on long translations or validation messages.

Production checklist

  • Use a consistent input baseline (border + ring focus).
  • Use space-y-* for predictable vertical rhythm.
  • Always show clear error messages, not only red borders.
  • Support disabled and read-only states.
  • Verify dark mode readability for borders, placeholders, and helper text.