Forms are critical to user interaction, yet they're often the most challenging to style well. Tailwind makes it easy to create beautiful, accessible forms with proper focus states, validation feedback, and interactive elements. In this lesson, you'll master styling inputs, selects, checkboxes, radio buttons, and build forms that are both functional and delightful to use.
Text Input Styling
Create polished text inputs with proper states:
Basic Text Input
<!-- Basic input -->
<input
type="text"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition"
placeholder="Enter text..."
>
<!-- Input with label -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Full Name
</label>
<input
type="text"
class="w-full px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-900 dark:text-white placeholder:text-gray-500 dark:placeholder:text-gray-400 focus:outline-none focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-800 focus:border-blue-500 dark:focus:border-blue-400 transition"
placeholder="John Doe"
>
</div>
<!-- Disabled input -->
<input
type="text"
disabled
class="w-full px-4 py-2 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-500 dark:text-gray-400 cursor-not-allowed"
placeholder="Disabled input"
>
<!-- Read-only input -->
<input
type="text"
readonly
value="Read-only value"
class="w-full px-4 py-2 bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-900 dark:text-white cursor-default"
>Input Variants
<!-- Small input -->
<input
type="text"
class="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition"
placeholder="Small input"
>
<!-- Default input -->
<input
type="text"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition"
placeholder="Default input"
>
<!-- Large input -->
<input
type="text"
class="w-full px-5 py-3 text-lg border border-gray-300 rounded-lg focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition"
placeholder="Large input"
>
<!-- Pill-shaped input -->
<input
type="text"
class="w-full px-6 py-2 border border-gray-300 rounded-full focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition"
placeholder="Pill input"
>Input with Icons
<!-- Input with leading icon -->
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
</div>
<input
type="text"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition"
placeholder="Search..."
>
</div>
<!-- Input with trailing icon -->
<div class="relative">
<input
type="email"
class="w-full pl-4 pr-10 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition"
placeholder="Email address"
>
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</div>
</div>
<!-- Input with both icons -->
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
</div>
<input
type="text"
class="w-full pl-10 pr-10 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition"
placeholder="Username"
>
<button class="absolute inset-y-0 right-0 pr-3 flex items-center">
<svg class="w-5 h-5 text-gray-400 hover:text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<!-- Input with addon -->
<div class="flex">
<span class="inline-flex items-center px-4 bg-gray-100 dark:bg-gray-700 border border-r-0 border-gray-300 dark:border-gray-600 rounded-l-lg text-gray-600 dark:text-gray-400">
https://
</span>
<input
type="text"
class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-r-lg focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition"
placeholder="example.com"
>
</div>♿ Accessibility Tip
Always provide clear focus indicators with focus:ring-4 and ensure sufficient color contrast. Never use outline-none without providing an alternative focus indicator!
Validation States
Show success, error, and warning states clearly:
Success State
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Email Address
</label>
<div class="relative">
<input
type="email"
class="w-full pl-4 pr-10 py-2 border-2 border-green-500 dark:border-green-400 rounded-lg bg-green-50 dark:bg-green-900/20 text-gray-900 dark:text-white focus:outline-none focus:ring-4 focus:ring-green-200 dark:focus:ring-green-800 transition"
value="john@example.com"
>
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg class="w-5 h-5 text-green-500 dark:text-green-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
</div>
</div>
<p class="mt-2 text-sm text-green-600 dark:text-green-400">
✓ Email is valid
</p>
</div>Error State
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Password
</label>
<div class="relative">
<input
type="password"
class="w-full pl-4 pr-10 py-2 border-2 border-red-500 dark:border-red-400 rounded-lg bg-red-50 dark:bg-red-900/20 text-gray-900 dark:text-white focus:outline-none focus:ring-4 focus:ring-red-200 dark:focus:ring-red-800 transition"
value="123"
>
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg class="w-5 h-5 text-red-500 dark:text-red-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
</svg>
</div>
</div>
<p class="mt-2 text-sm text-red-600 dark:text-red-400">
Password must be at least 8 characters
</p>
</div>Warning State
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Username
</label>
<div class="relative">
<input
type="text"
class="w-full pl-4 pr-10 py-2 border-2 border-yellow-500 dark:border-yellow-400 rounded-lg bg-yellow-50 dark:bg-yellow-900/20 text-gray-900 dark:text-white focus:outline-none focus:ring-4 focus:ring-yellow-200 dark:focus:ring-yellow-800 transition"
value="john_doe"
>
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg class="w-5 h-5 text-yellow-500 dark:text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
</svg>
</div>
</div>
<p class="mt-2 text-sm text-yellow-600 dark:text-yellow-400">
Username contains underscores - consider using hyphens instead
</p>
</div>Textarea Styling
Style multi-line text inputs:
<!-- Basic textarea -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Message
</label>
<textarea
rows="4"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder:text-gray-500 dark:placeholder:text-gray-400 focus:outline-none focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-800 focus:border-blue-500 dark:focus:border-blue-400 transition resize-none"
placeholder="Enter your message..."
></textarea>
</div>
<!-- Textarea with character count -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Description
</label>
<textarea
rows="4"
maxlength="200"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition resize-none"
placeholder="Enter description..."
></textarea>
<div class="flex justify-between mt-2">
<p class="text-sm text-gray-600 dark:text-gray-400">Maximum 200 characters</p>
<p class="text-sm text-gray-600 dark:text-gray-400">
<span class="font-semibold">0</span> / 200
</p>
</div>
</div>
<!-- Resizable textarea -->
<textarea
rows="4"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition resize-y"
placeholder="Resizable vertically..."
></textarea>
<!-- Auto-expanding textarea (requires JavaScript) -->
<textarea
rows="3"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition resize-none"
placeholder="Auto-expands as you type..."
oninput="this.style.height = 'auto'; this.style.height = this.scrollHeight + 'px'"
></textarea>Select Dropdown Styling
Create styled select dropdowns:
<!-- Basic select -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Country
</label>
<select class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-800 focus:border-blue-500 dark:focus:border-blue-400 transition">
<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="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Category
</label>
<div class="relative">
<select class="w-full px-4 py-2 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white appearance-none focus:outline-none focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-800 focus:border-blue-500 dark:focus:border-blue-400 transition cursor-pointer">
<option value="">Choose category</option>
<option value="tech">Technology</option>
<option value="business">Business</option>
<option value="design">Design</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
</div>
</div>
<!-- Disabled select -->
<select
disabled
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed"
>
<option>Disabled select</option>
</select>
<!-- Select with groups -->
<select class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 focus:outline-none focus:ring-4 focus:ring-blue-200 focus:border-blue-500 transition">
<option value="">Select framework</option>
<optgroup label="Frontend">
<option value="react">React</option>
<option value="vue">Vue</option>
<option value="angular">Angular</option>
</optgroup>
<optgroup label="Backend">
<option value="node">Node.js</option>
<option value="django">Django</option>
<option value="rails">Rails</option>
</optgroup>
</select>Checkboxes & Radio Buttons
Style checkboxes and radio buttons:
Checkboxes
<!-- Basic checkbox -->
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-4 focus:ring-blue-200 focus:ring-offset-0"
>
<span class="text-gray-700 dark:text-gray-300">I agree to the terms</span>
</label>
<!-- Checkbox list -->
<div class="space-y-3">
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked
class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-4 focus:ring-blue-200"
>
<span class="text-gray-700 dark:text-gray-300">Option 1</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-4 focus:ring-blue-200"
>
<span class="text-gray-700 dark:text-gray-300">Option 2</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-4 focus:ring-blue-200"
>
<span class="text-gray-700 dark:text-gray-300">Option 3</span>
</label>
</div>
<!-- Disabled checkbox -->
<label class="flex items-center gap-2 cursor-not-allowed opacity-60">
<input
type="checkbox"
disabled
class="w-5 h-5 text-gray-400 border-gray-300 rounded"
>
<span class="text-gray-500">Disabled option</span>
</label>
<!-- Checkbox with description -->
<label class="flex items-start gap-3 cursor-pointer p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition">
<input
type="checkbox"
class="w-5 h-5 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-4 focus:ring-blue-200"
>
<div>
<p class="font-semibold text-gray-900 dark:text-white">Email notifications</p>
<p class="text-sm text-gray-600 dark:text-gray-400">Receive updates about your account activity</p>
</div>
</label>Radio Buttons
<!-- Basic radio group -->
<div class="space-y-3">
<label class="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="plan"
value="free"
checked
class="w-5 h-5 text-blue-600 border-gray-300 focus:ring-4 focus:ring-blue-200"
>
<span class="text-gray-700 dark:text-gray-300">Free Plan</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="plan"
value="pro"
class="w-5 h-5 text-blue-600 border-gray-300 focus:ring-4 focus:ring-blue-200"
>
<span class="text-gray-700 dark:text-gray-300">Pro Plan</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="plan"
value="enterprise"
class="w-5 h-5 text-blue-600 border-gray-300 focus:ring-4 focus:ring-blue-200"
>
<span class="text-gray-700 dark:text-gray-300">Enterprise Plan</span>
</label>
</div>
<!-- Radio cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<label class="relative flex cursor-pointer">
<input
type="radio"
name="tier"
value="basic"
class="peer sr-only"
>
<div class="w-full p-6 border-2 border-gray-300 dark:border-gray-600 rounded-lg peer-checked:border-blue-500 peer-checked:bg-blue-50 dark:peer-checked:bg-blue-900/20 peer-focus:ring-4 peer-focus:ring-blue-200 transition">
<p class="text-xl font-bold text-gray-900 dark:text-white mb-2">Basic</p>
<p class="text-3xl font-bold text-gray-900 dark:text-white mb-2">$9</p>
<p class="text-sm text-gray-600 dark:text-gray-400">per month</p>
</div>
</label>
<label class="relative flex cursor-pointer">
<input
type="radio"
name="tier"
value="pro"
checked
class="peer sr-only"
>
<div class="w-full p-6 border-2 border-gray-300 dark:border-gray-600 rounded-lg peer-checked:border-blue-500 peer-checked:bg-blue-50 dark:peer-checked:bg-blue-900/20 peer-focus:ring-4 peer-focus:ring-blue-200 transition">
<p class="text-xl font-bold text-gray-900 dark:text-white mb-2">Pro</p>
<p class="text-3xl font-bold text-gray-900 dark:text-white mb-2">$29</p>
<p class="text-sm text-gray-600 dark:text-gray-400">per month</p>
</div>
</label>
<label class="relative flex cursor-pointer">
<input
type="radio"
name="tier"
value="enterprise"
class="peer sr-only"
>
<div class="w-full p-6 border-2 border-gray-300 dark:border-gray-600 rounded-lg peer-checked:border-blue-500 peer-checked:bg-blue-50 dark:peer-checked:bg-blue-900/20 peer-focus:ring-4 peer-focus:ring-blue-200 transition">
<p class="text-xl font-bold text-gray-900 dark:text-white mb-2">Enterprise</p>
<p class="text-3xl font-bold text-gray-900 dark:text-white mb-2">$99</p>
<p class="text-sm text-gray-600 dark:text-gray-400">per month</p>
</div>
</label>
</div>💡 Checkbox & Radio Best Practices
- Always wrap in
<label>for larger click areas - Use
cursor-pointeron labels - Provide clear focus states with
focus:ring-4 - Consider using card-style options for important choices
Toggle Switches
Create custom toggle switches:
<!-- Basic toggle switch -->
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
<span class="ms-3 text-sm font-medium text-gray-900 dark:text-gray-300">Toggle me</span>
</label>
<!-- Larger toggle switch -->
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer">
<div class="w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:start-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all peer-checked:bg-blue-600"></div>
<span class="ms-3 text-base font-medium text-gray-900 dark:text-gray-300">Large toggle</span>
</label>
<!-- Toggle with description -->
<label class="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 transition">
<div class="flex-1">
<p class="font-semibold text-gray-900 dark:text-white">Enable notifications</p>
<p class="text-sm text-gray-600 dark:text-gray-400">Receive email updates about your account</p>
</div>
<div class="relative inline-flex items-center">
<input type="checkbox" class="sr-only peer">
<div class="w-11 h-6 bg-gray-200 peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
</div>
</label>
<!-- Disabled toggle -->
<label class="relative inline-flex items-center cursor-not-allowed opacity-60">
<input type="checkbox" disabled class="sr-only peer">
<div class="w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all"></div>
<span class="ms-3 text-sm font-medium text-gray-500">Disabled toggle</span>
</label>File Input Styling
Create custom file upload interfaces:
<!-- Custom file upload button -->
<label class="cursor-pointer">
<input type="file" class="sr-only">
<div class="inline-flex items-center gap-2 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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"/>
</svg>
<span>Choose File</span>
</div>
</label>
<!-- File upload dropzone -->
<label class="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 dark:border-gray-600 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition">
<input type="file" class="sr-only">
<div class="flex flex-col items-center justify-center pt-5 pb-6">
<svg class="w-12 h-12 mb-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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"/>
</svg>
<p class="mb-2 text-sm text-gray-600 dark:text-gray-400">
<span class="font-semibold">Click to upload</span> or drag and drop
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">PNG, JPG or GIF (MAX. 10MB)</p>
</div>
</label>
<!-- File upload with preview -->
<div class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-6">
<label class="cursor-pointer">
<input type="file" accept="image/*" class="sr-only">
<div class="flex flex-col items-center">
<div class="w-32 h-32 mb-4 bg-gray-100 dark:bg-gray-700 rounded-lg flex items-center justify-center">
<svg class="w-12 h-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
</div>
<button type="button" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg transition">
Upload Image
</button>
</div>
</label>
</div>Complete Form Example
Put it all together in a complete form:
<form class="max-w-2xl mx-auto bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8">
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">Create Account</h2>
<!-- Name fields -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
First Name *
</label>
<input
type="text"
required
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-800 focus:border-blue-500 transition"
placeholder="John"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Last Name *
</label>
<input
type="text"
required
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-800 focus:border-blue-500 transition"
placeholder="Doe"
>
</div>
</div>
<!-- Email -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Email Address *
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</div>
<input
type="email"
required
class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder:text-gray-500 focus:outline-none focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-800 focus:border-blue-500 transition"
placeholder="john@example.com"
>
</div>
</div>
<!-- Password -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Password *
</label>
<input
type="password"
required
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-800 focus:border-blue-500 transition"
placeholder="••••••••"
>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
Must be at least 8 characters
</p>
</div>
<!-- Country -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Country *
</label>
<div class="relative">
<select class="w-full px-4 py-2 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white appearance-none focus:outline-none focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-800 focus:border-blue-500 transition cursor-pointer">
<option value="">Select your country</option>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="ca">Canada</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
</div>
</div>
<!-- Bio -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Bio
</label>
<textarea
rows="4"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder:text-gray-500 focus:outline-none focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-800 focus:border-blue-500 transition resize-none"
placeholder="Tell us about yourself..."
></textarea>
</div>
<!-- Preferences -->
<div class="mb-6">
<p class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
Notifications
</p>
<div class="space-y-3">
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked
class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-4 focus:ring-blue-200"
>
<span class="text-gray-700 dark:text-gray-300">Email notifications</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-4 focus:ring-blue-200"
>
<span class="text-gray-700 dark:text-gray-300">SMS notifications</span>
</label>
</div>
</div>
<!-- Terms -->
<div class="mb-6">
<label class="flex items-start gap-2 cursor-pointer">
<input
type="checkbox"
required
class="w-5 h-5 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-4 focus:ring-blue-200"
>
<span class="text-sm text-gray-700 dark:text-gray-300">
I agree to the <a href="#" class="text-blue-600 hover:text-blue-700 dark:text-blue-400">Terms of Service</a> and <a href="#" class="text-blue-600 hover:text-blue-700 dark:text-blue-400">Privacy Policy</a>
</span>
</label>
</div>
<!-- Submit -->
<div class="flex gap-4">
<button
type="submit"
class="flex-1 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg shadow hover:shadow-lg transition-all focus:outline-none focus:ring-4 focus:ring-blue-300"
>
Create Account
</button>
<button
type="button"
class="px-6 py-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-900 dark:text-white font-semibold rounded-lg transition-all"
>
Cancel
</button>
</div>
</form>Forms Playground
Practice building forms:
Build Beautiful Forms
Practice styling form elements
Form Best Practices
Follow These Guidelines:
- Always provide focus states: Use
focus:ring-4for keyboard navigation - Label all inputs: Use semantic
<label>elements - Show validation clearly: Use color + icon + message
- Provide helpful placeholders: Show format examples
- Make interactive elements obvious: Use cursor-pointer
- Test keyboard navigation: Ensure all elements are accessible
- Use appropriate input types: email, tel, url, etc.
- Group related fields: Use fieldsets for better organization
Key Takeaways
- Always provide clear focus states with
focus:ring-4 - Use
placeholder:variant to style placeholder text - Show validation with border colors, icons, and messages
- Wrap checkboxes/radios in labels for larger click areas
- Use
appearance-nonefor custom select styling - Create custom file uploads with hidden inputs
- Toggle switches use
peerutilities for styling - Test all forms with keyboard navigation
- Ensure sufficient color contrast for accessibility
- Combine multiple validation indicators (color + icon + text)
What's Next?
You've mastered form styling! You can now create beautiful, accessible forms with proper validation and interactive states.
In the final lesson, we'll explore Production Best Practices—learning optimization techniques, file organization, maintainability strategies, and how to deploy Tailwind projects to production efficiently.
🎯 Practice Challenge!
Build a complete multi-step registration form! Include:
- Personal information step (name, email, password)
- Address step (street, city, country select)
- Preferences step (checkboxes, radio buttons, toggles)
- Review step (show all data before submission)
- Validation on each field with proper error states
- Progress indicator showing current step
- Previous/Next navigation buttons
- Full keyboard accessibility