Forms & Input Styling
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
forandidattributes - Use appropriate input types (
email,tel,number) for better mobile keyboards - Add
required,pattern,min,maxfor 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.