Tailwind CSS

Forms & Input Styling

20 min Lesson 20 of 35

Forms & Input Styling in Tailwind CSS

Forms are critical user interface elements that require careful styling for usability and accessibility. Tailwind provides comprehensive utilities for styling form elements, and the official @tailwindcss/forms plugin offers beautiful, accessible defaults for all input types.

Form Accessibility First

Always use proper form semantics: labels associated with inputs via for and id attributes, fieldsets for grouping, and appropriate input types. Accessibility ensures all users can interact with your forms effectively.

The @tailwindcss/forms Plugin

The official forms plugin provides opinionated, accessible base styles for form controls. It resets default browser styles and provides a solid foundation for customization.

Installing @tailwindcss/forms

<!-- Install via npm -->
npm install -D @tailwindcss/forms

<!-- Add to tailwind.config.js -->
module.exports = {
  plugins: [
    require('@tailwindcss/forms'),
  ],
}

<!-- Or with strategy option -->
module.exports = {
  plugins: [
    require('@tailwindcss/forms')({
      strategy: 'base', // 'base' or 'class'
    }),
  ],
}

<!-- Strategy 'base': applies styles to all inputs globally
     Strategy 'class': requires form-{type} classes on inputs -->

Plugin Strategies

Use strategy: 'base' (default) to style all form elements automatically. Use strategy: 'class' if you need more control and want to opt-in specific elements using classes like form-input, form-select, etc.

Text Inputs

Basic Text Input Styling

<!-- Simple text input -->
<div class="mb-4">
  <label for="name" class="block text-sm font-medium text-gray-700 mb-2">
    Full Name
  </label>
  <input type="text"
         id="name"
         name="name"
         class="w-full px-4 py-2 border border-gray-300 rounded-lg
                focus:ring-2 focus:ring-blue-500 focus:border-transparent"
         placeholder="Enter your name">
</div>

<!-- Email input -->
<div class="mb-4">
  <label for="email" class="block text-sm font-medium text-gray-700 mb-2">
    Email Address
  </label>
  <input type="email"
         id="email"
         name="email"
         required
         class="w-full px-4 py-2 border border-gray-300 rounded-lg
                focus:ring-2 focus:ring-blue-500 focus:border-transparent"
         placeholder="you@example.com">
</div>

<!-- Password input -->
<div class="mb-4">
  <label for="password" class="block text-sm font-medium text-gray-700 mb-2">
    Password
  </label>
  <input type="password"
         id="password"
         name="password"
         required
         class="w-full px-4 py-2 border border-gray-300 rounded-lg
                focus:ring-2 focus:ring-blue-500 focus:border-transparent"
         placeholder="••••••••">
</div>

Input Sizes

<!-- Small input -->
<input type="text"
       class="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg
              focus:ring-2 focus:ring-blue-500 focus:border-transparent"
       placeholder="Small input">

<!-- Medium input (default) -->
<input type="text"
       class="w-full px-4 py-2 border border-gray-300 rounded-lg
              focus:ring-2 focus:ring-blue-500 focus:border-transparent"
       placeholder="Medium input">

<!-- Large input -->
<input type="text"
       class="w-full px-5 py-3 text-lg border border-gray-300 rounded-lg
              focus:ring-2 focus:ring-blue-500 focus:border-transparent"
       placeholder="Large input">

Focus Rings and States

Custom Focus Styles

<!-- Blue focus ring (default) -->
<input type="text"
       class="w-full px-4 py-2 border border-gray-300 rounded-lg
              focus:ring-2 focus:ring-blue-500 focus:border-blue-500">

<!-- Purple focus ring -->
<input type="text"
       class="w-full px-4 py-2 border border-gray-300 rounded-lg
              focus:ring-2 focus:ring-purple-500 focus:border-purple-500">

<!-- No focus ring (not recommended for accessibility) -->
<input type="text"
       class="w-full px-4 py-2 border border-gray-300 rounded-lg
              focus:outline-none focus:border-blue-500">

<!-- Offset focus ring -->
<input type="text"
       class="w-full px-4 py-2 border border-gray-300 rounded-lg
              focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">

Validation States

<!-- Success state -->
<div class="mb-4">
  <label for="valid-input" class="block text-sm font-medium text-green-700 mb-2">
    Email (Valid)
  </label>
  <input type="email"
         id="valid-input"
         class="w-full px-4 py-2 border-2 border-green-500 rounded-lg
                focus:ring-2 focus:ring-green-500 focus:border-transparent
                bg-green-50"
         value="user@example.com">
  <p class="mt-2 text-sm text-green-600">
    ✓ Email address is valid
  </p>
</div>

<!-- Error state -->
<div class="mb-4">
  <label for="error-input" class="block text-sm font-medium text-red-700 mb-2">
    Email (Invalid)
  </label>
  <input type="email"
         id="error-input"
         class="w-full px-4 py-2 border-2 border-red-500 rounded-lg
                focus:ring-2 focus:ring-red-500 focus:border-transparent
                bg-red-50"
         value="invalid-email">
  <p class="mt-2 text-sm text-red-600">
    ✗ Please enter a valid email address
  </p>
</div>

<!-- Warning state -->
<div class="mb-4">
  <label for="warning-input" class="block text-sm font-medium text-yellow-700 mb-2">
    Password
  </label>
  <input type="password"
         id="warning-input"
         class="w-full px-4 py-2 border-2 border-yellow-500 rounded-lg
                focus:ring-2 focus:ring-yellow-500 focus:border-transparent
                bg-yellow-50">
  <p class="mt-2 text-sm text-yellow-600">
    ⚠ Password is weak, consider using special characters
  </p>
</div>

Disabled vs. Readonly

disabled inputs cannot be focused or submitted and appear faded. readonly inputs can be focused and submitted but cannot be edited. Choose based on your use case: use disabled for unavailable fields, readonly for fields that show data but shouldn't be changed.

Textareas

Textarea Styling

<!-- Basic textarea -->
<div class="mb-4">
  <label for="message" class="block text-sm font-medium text-gray-700 mb-2">
    Message
  </label>
  <textarea id="message"
            name="message"
            rows="4"
            class="w-full px-4 py-2 border border-gray-300 rounded-lg
                   focus:ring-2 focus:ring-blue-500 focus:border-transparent
                   resize-none"
            placeholder="Enter your message here..."></textarea>
  <p class="mt-2 text-sm text-gray-500">
    Maximum 500 characters
  </p>
</div>

<!-- Auto-resizing textarea (with resize utilities) -->
<textarea class="w-full px-4 py-2 border border-gray-300 rounded-lg
                 focus:ring-2 focus:ring-blue-500
                 resize-y"
          placeholder="Resizable vertically"></textarea>

<textarea class="w-full px-4 py-2 border border-gray-300 rounded-lg
                 focus:ring-2 focus:ring-blue-500
                 resize-x"
          placeholder="Resizable horizontally"></textarea>

<textarea class="w-full px-4 py-2 border border-gray-300 rounded-lg
                 focus:ring-2 focus:ring-blue-500
                 resize"
          placeholder="Resizable both directions"></textarea>

Select Dropdowns

Select Styling

<!-- Basic select -->
<div class="mb-4">
  <label for="country" class="block text-sm font-medium text-gray-700 mb-2">
    Country
  </label>
  <select id="country"
          name="country"
          class="w-full px-4 py-2 border border-gray-300 rounded-lg
                 focus:ring-2 focus:ring-blue-500 focus:border-transparent
                 bg-white">
    <option value="">Select a country</option>
    <option value="us">United States</option>
    <option value="uk">United Kingdom</option>
    <option value="ca">Canada</option>
    <option value="au">Australia</option>
  </select>
</div>

<!-- Select with custom arrow -->
<div class="relative">
  <select class="w-full px-4 py-2 pr-10 border border-gray-300 rounded-lg
                 focus:ring-2 focus:ring-blue-500 focus:border-transparent
                 appearance-none bg-white">
    <option>Option 1</option>
    <option>Option 2</option>
    <option>Option 3</option>
  </select>
  <div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
    <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor">
      <path d="M19 9l-7 7-7-7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
    </svg>
  </div>
</div>

<!-- Multiple select -->
<select multiple
        size="5"
        class="w-full px-4 py-2 border border-gray-300 rounded-lg
               focus:ring-2 focus:ring-blue-500">
  <option>Option 1</option>
  <option>Option 2</option>
  <option>Option 3</option>
  <option>Option 4</option>
  <option>Option 5</option>
</select>

Checkboxes and Radio Buttons

Checkbox Styling

<!-- Basic checkbox -->
<div class="flex items-center mb-4">
  <input type="checkbox"
         id="terms"
         name="terms"
         class="w-4 h-4 text-blue-600 border-gray-300 rounded
                focus:ring-2 focus:ring-blue-500">
  <label for="terms" class="ml-2 text-sm text-gray-700">
    I agree to the terms and conditions
  </label>
</div>

<!-- Custom styled checkbox -->
<div class="flex items-center mb-4">
  <input type="checkbox"
         id="custom-check"
         class="w-5 h-5 text-purple-600 border-gray-300 rounded-md
                focus:ring-2 focus:ring-purple-500">
  <label for="custom-check" class="ml-3 text-sm font-medium text-gray-900">
    Subscribe to newsletter
  </label>
</div>

<!-- Checkbox group -->
<fieldset class="mb-4">
  <legend class="text-sm font-medium text-gray-700 mb-3">
    Select your interests
  </legend>
  <div class="space-y-2">
    <div class="flex items-center">
      <input type="checkbox" id="tech" value="tech"
             class="w-4 h-4 text-blue-600 border-gray-300 rounded
                    focus:ring-2 focus:ring-blue-500">
      <label for="tech" class="ml-2 text-sm text-gray-700">
        Technology
      </label>
    </div>
    <div class="flex items-center">
      <input type="checkbox" id="design" value="design"
             class="w-4 h-4 text-blue-600 border-gray-300 rounded
                    focus:ring-2 focus:ring-blue-500">
      <label for="design" class="ml-2 text-sm text-gray-700">
        Design
      </label>
    </div>
    <div class="flex items-center">
      <input type="checkbox" id="business" value="business"
             class="w-4 h-4 text-blue-600 border-gray-300 rounded
                    focus:ring-2 focus:ring-blue-500">
      <label for="business" class="ml-2 text-sm text-gray-700">
        Business
      </label>
    </div>
  </div>
</fieldset>

Radio Button Styling

<!-- Basic radio buttons -->
<fieldset class="mb-4">
  <legend class="text-sm font-medium text-gray-700 mb-3">
    Choose a plan
  </legend>
  <div class="space-y-2">
    <div class="flex items-center">
      <input type="radio"
             id="basic"
             name="plan"
             value="basic"
             class="w-4 h-4 text-blue-600 border-gray-300
                    focus:ring-2 focus:ring-blue-500">
      <label for="basic" class="ml-2 text-sm text-gray-700">
        Basic - $9/month
      </label>
    </div>
    <div class="flex items-center">
      <input type="radio"
             id="pro"
             name="plan"
             value="pro"
             checked
             class="w-4 h-4 text-blue-600 border-gray-300
                    focus:ring-2 focus:ring-blue-500">
      <label for="pro" class="ml-2 text-sm text-gray-700">
        Pro - $29/month
      </label>
    </div>
    <div class="flex items-center">
      <input type="radio"
             id="enterprise"
             name="plan"
             value="enterprise"
             class="w-4 h-4 text-blue-600 border-gray-300
                    focus:ring-2 focus:ring-blue-500">
      <label for="enterprise" class="ml-2 text-sm text-gray-700">
        Enterprise - $99/month
      </label>
    </div>
  </div>
</fieldset>

<!-- Card-style radio buttons -->
<div class="space-y-3">
  <label class="flex items-center p-4 border-2 border-gray-200 rounded-lg
                cursor-pointer hover:border-blue-500 transition-colors
                has-[:checked]:border-blue-500 has-[:checked]:bg-blue-50">
    <input type="radio" name="plan-card" value="basic"
           class="w-4 h-4 text-blue-600">
    <div class="ml-3 flex-1">
      <div class="font-medium text-gray-900">Basic Plan</div>
      <div class="text-sm text-gray-500">Perfect for individuals</div>
    </div>
    <div class="text-xl font-bold text-gray-900">$9</div>
  </label>

  <label class="flex items-center p-4 border-2 border-gray-200 rounded-lg
                cursor-pointer hover:border-blue-500 transition-colors
                has-[:checked]:border-blue-500 has-[:checked]:bg-blue-50">
    <input type="radio" name="plan-card" value="pro"
           class="w-4 h-4 text-blue-600">
    <div class="ml-3 flex-1">
      <div class="font-medium text-gray-900">Pro Plan</div>
      <div class="text-sm text-gray-500">Best for teams</div>
    </div>
    <div class="text-xl font-bold text-gray-900">$29</div>
  </label>
</div>

File Input Styling

Custom File Input

<!-- Basic file input (hard to style) -->
<input type="file"
       class="block w-full text-sm text-gray-500
              file:mr-4 file:py-2 file:px-4
              file:rounded-lg file:border-0
              file:text-sm file:font-semibold
              file:bg-blue-50 file:text-blue-700
              hover:file:bg-blue-100">

<!-- Custom file upload with hidden input -->
<div class="mb-4">
  <label class="block text-sm font-medium text-gray-700 mb-2">
    Upload Document
  </label>
  <div class="flex items-center justify-center w-full">
    <label class="flex flex-col items-center justify-center w-full h-32
                  border-2 border-gray-300 border-dashed rounded-lg
                  cursor-pointer bg-gray-50 hover:bg-gray-100">
      <div class="flex flex-col items-center justify-center pt-5 pb-6">
        <svg class="w-8 h-8 mb-4 text-gray-500" fill="none" stroke="currentColor">
          <path d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
                stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
        </svg>
        <p class="mb-2 text-sm text-gray-500">
          <span class="font-semibold">Click to upload</span> or drag and drop
        </p>
        <p class="text-xs text-gray-500">PDF, DOC, or DOCX (MAX. 10MB)</p>
      </div>
      <input type="file" class="hidden" accept=".pdf,.doc,.docx">
    </label>
  </div>
</div>

Form Layout Patterns

Complete Registration Form

<form class="max-w-lg mx-auto bg-white rounded-lg shadow-lg p-8">
  <h2 class="text-2xl font-bold text-gray-900 mb-6">Create Account</h2>

  <!-- Full Name -->
  <div class="mb-4">
    <label for="fullname" class="block text-sm font-medium text-gray-700 mb-2">
      Full Name
    </label>
    <input type="text"
           id="fullname"
           required
           class="w-full px-4 py-2 border border-gray-300 rounded-lg
                  focus:ring-2 focus:ring-blue-500 focus:border-transparent"
           placeholder="John Doe">
  </div>

  <!-- Email -->
  <div class="mb-4">
    <label for="email-reg" class="block text-sm font-medium text-gray-700 mb-2">
      Email Address
    </label>
    <input type="email"
           id="email-reg"
           required
           class="w-full px-4 py-2 border border-gray-300 rounded-lg
                  focus:ring-2 focus:ring-blue-500 focus:border-transparent"
           placeholder="john@example.com">
  </div>

  <!-- Password -->
  <div class="mb-4">
    <label for="password-reg" class="block text-sm font-medium text-gray-700 mb-2">
      Password
    </label>
    <input type="password"
           id="password-reg"
           required
           class="w-full px-4 py-2 border border-gray-300 rounded-lg
                  focus:ring-2 focus:ring-blue-500 focus:border-transparent"
           placeholder="••••••••">
    <p class="mt-2 text-xs text-gray-500">
      Must be at least 8 characters with uppercase, lowercase, and numbers
    </p>
  </div>

  <!-- Terms checkbox -->
  <div class="mb-6">
    <div class="flex items-start">
      <input type="checkbox"
             id="terms-reg"
             required
             class="w-4 h-4 mt-1 text-blue-600 border-gray-300 rounded
                    focus:ring-2 focus:ring-blue-500">
      <label for="terms-reg" class="ml-2 text-sm text-gray-700">
        I agree to the <a href="#" class="text-blue-600 hover:underline">Terms of Service</a>
        and <a href="#" class="text-blue-600 hover:underline">Privacy Policy</a>
      </label>
    </div>
  </div>

  <!-- Submit button -->
  <button type="submit"
          class="w-full bg-blue-600 text-white py-3 px-4 rounded-lg font-medium
                 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300
                 transition-colors duration-200">
    Create Account
  </button>

  <p class="mt-4 text-center text-sm text-gray-600">
    Already have an account?
    <a href="#" class="text-blue-600 hover:underline font-medium">Sign in</a>
  </p>
</form>

Two-Column Form Layout

<form class="max-w-4xl mx-auto bg-white rounded-lg shadow-lg p-8">
  <h2 class="text-2xl font-bold text-gray-900 mb-6">Contact Information</h2>

  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
    <!-- First Name -->
    <div>
      <label for="firstname" class="block text-sm font-medium text-gray-700 mb-2">
        First Name
      </label>
      <input type="text" id="firstname"
             class="w-full px-4 py-2 border border-gray-300 rounded-lg
                    focus:ring-2 focus:ring-blue-500 focus:border-transparent">
    </div>

    <!-- Last Name -->
    <div>
      <label for="lastname" class="block text-sm font-medium text-gray-700 mb-2">
        Last Name
      </label>
      <input type="text" id="lastname"
             class="w-full px-4 py-2 border border-gray-300 rounded-lg
                    focus:ring-2 focus:ring-blue-500 focus:border-transparent">
    </div>

    <!-- Email (full width) -->
    <div class="md:col-span-2">
      <label for="email-contact" class="block text-sm font-medium text-gray-700 mb-2">
        Email Address
      </label>
      <input type="email" id="email-contact"
             class="w-full px-4 py-2 border border-gray-300 rounded-lg
                    focus:ring-2 focus:ring-blue-500 focus:border-transparent">
    </div>

    <!-- Phone -->
    <div>
      <label for="phone" class="block text-sm font-medium text-gray-700 mb-2">
        Phone Number
      </label>
      <input type="tel" id="phone"
             class="w-full px-4 py-2 border border-gray-300 rounded-lg
                    focus:ring-2 focus:ring-blue-500 focus:border-transparent">
    </div>

    <!-- Company -->
    <div>
      <label for="company" class="block text-sm font-medium text-gray-700 mb-2">
        Company
      </label>
      <input type="text" id="company"
             class="w-full px-4 py-2 border border-gray-300 rounded-lg
                    focus:ring-2 focus:ring-blue-500 focus:border-transparent">
    </div>
  </div>

  <div class="mt-6">
    <button type="submit"
            class="bg-blue-600 text-white py-3 px-8 rounded-lg font-medium
                   hover:bg-blue-700 focus:ring-4 focus:ring-blue-300
                   transition-colors duration-200">
      Submit
    </button>
  </div>
</form>

Exercise 1: Login Form with Validation

Create a complete login form with visual validation:

  • Email and password fields with proper labels
  • "Remember me" checkbox
  • "Forgot password?" link
  • Show validation states: valid (green), invalid (red)
  • Display error messages below invalid fields
  • Submit button that disables during submission
  • Responsive layout (mobile-first)

Exercise 2: Multi-Step Form

Build a 3-step registration wizard:

  • Step 1: Personal info (name, email, phone)
  • Step 2: Plan selection (radio buttons with card styling)
  • Step 3: Payment details (card number, expiry, CVV)
  • Progress indicator showing current step
  • Previous/Next navigation buttons
  • Form validation on each step
  • Summary review on final step

Exercise 3: Advanced Search Form

Create a comprehensive search form:

  • Text search input with icon
  • Category dropdown select
  • Price range with two number inputs (min/max)
  • Checkbox group for filters (multiple selections)
  • Date range picker (two date inputs)
  • Sort by dropdown
  • Clear filters button and Search button
  • Collapsible on mobile (show/hide filters)

Form Best Practices

  • Always associate labels with inputs using for and id attributes
  • Use appropriate input types (email, tel, number) for better mobile keyboards
  • Add required, pattern, min, max for HTML5 validation
  • Provide clear error messages next to invalid fields
  • Use placeholder text for format examples, not as label replacements
  • Ensure sufficient color contrast for all text (WCAG AA: 4.5:1 minimum)
  • Make focus states highly visible for keyboard navigation
  • Test forms with keyboard-only navigation
  • Group related fields with <fieldset> and <legend>
  • Use autocomplete attributes to help users fill forms faster

Summary

Tailwind provides comprehensive tools for creating accessible, beautiful forms:

  • @tailwindcss/forms Plugin: Provides consistent, accessible base styles for all form elements
  • Text Inputs: Full styling control with focus rings, sizes, and states
  • Focus Management: Customizable focus rings for better accessibility (focus:ring-{n})
  • Validation States: Visual feedback for success, error, and warning states
  • Textareas: Control resizing with resize-{direction} utilities
  • Selects: Style dropdowns with custom arrows using appearance-none
  • Checkboxes & Radios: Full customization with color and size utilities
  • File Inputs: Style with file: modifier or create custom upload areas
  • Form Layouts: Grid and Flexbox for responsive, organized form structures

Well-designed forms are crucial for conversions and user satisfaction. Always prioritize accessibility, provide clear feedback, and ensure forms work seamlessly across all devices and input methods.