Hover, Focus & State Variants
Hover, Focus & State Variants in Tailwind CSS
Tailwind CSS provides powerful state variant modifiers that allow you to conditionally apply styles based on user interaction states, element states, and more. These variants make it incredibly easy to create interactive and accessible user interfaces without writing custom CSS.
Basic Interaction States
The most common state variants are for mouse interactions. Tailwind makes it simple to change styles when users hover over, click on, or focus on elements.
Hover State
The hover: modifier applies styles when the user hovers over an element with their mouse:
Basic Hover Examples
<!-- Hover background change -->
<button class="bg-blue-500 hover:bg-blue-700 text-white px-4 py-2 rounded">
Hover Me
</button>
<!-- Hover text color change -->
<a href="#" class="text-blue-600 hover:text-blue-800 underline">
Hover Link
</a>
<!-- Multiple hover effects -->
<div class="bg-white hover:bg-gray-100 hover:shadow-lg transition-all p-4">
Hover for multiple effects
</div>
<!-- Hover scale effect -->
<img src="image.jpg" class="hover:scale-110 transition-transform" alt="Image">
hover: modifier works on any utility class. You can combine it with colors, spacing, transforms, shadows, and more.
Focus State
The focus: modifier applies styles when an element receives keyboard focus, essential for accessibility:
Focus State Examples
<!-- Input field with focus styles -->
<input
type="text"
class="border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 px-4 py-2 rounded"
placeholder="Focus on me"
>
<!-- Button with focus ring -->
<button class="bg-green-500 text-white px-4 py-2 rounded focus:outline-none focus:ring-4 focus:ring-green-300">
Click Me
</button>
<!-- Link with focus styles -->
<a href="#" class="text-blue-600 focus:text-blue-800 focus:underline">
Tab to focus
</a>
focus:ring utilities are perfect for this.
Active State
The active: modifier applies styles when an element is being clicked or pressed:
Active State Examples
<!-- Button with active state -->
<button class="bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white px-6 py-3 rounded">
Press Me
</button>
<!-- Scale down on click -->
<button class="bg-green-500 active:scale-95 transition-transform px-4 py-2 rounded text-white">
Click to scale
</button>
<!-- Combined states -->
<button class="bg-purple-500 hover:bg-purple-600 active:bg-purple-800 focus:ring-4 focus:ring-purple-300 text-white px-4 py-2 rounded">
All States
</button>
Advanced Focus Variants
Focus-Within
The focus-within: modifier applies styles to a parent element when any of its children receive focus:
Focus-Within Examples
<!-- Form container that highlights when any input is focused -->
<div class="border-2 border-gray-300 focus-within:border-blue-500 focus-within:shadow-lg p-4 rounded">
<label class="block mb-2">Name</label>
<input type="text" class="border px-3 py-2 rounded w-full">
</div>
<!-- Search container with focus-within -->
<div class="flex items-center bg-gray-100 focus-within:bg-white focus-within:ring-2 focus-within:ring-blue-300 rounded-lg px-4 py-2">
<svg class="w-5 h-5 text-gray-400">...</svg>
<input type="search" class="bg-transparent focus:outline-none ml-2" placeholder="Search...">
</div>
Focus-Visible
The focus-visible: modifier only applies styles when an element receives keyboard focus, not mouse click focus:
Focus-Visible Examples
<!-- Only shows focus ring when tabbing, not clicking -->
<button class="bg-blue-500 text-white px-4 py-2 rounded focus:outline-none focus-visible:ring-4 focus-visible:ring-blue-300">
Tab to see ring
</button>
<!-- Link with keyboard-only focus indicator -->
<a href="#" class="text-blue-600 focus-visible:underline focus-visible:ring-2 focus-visible:ring-blue-300 rounded">
Keyboard focus only
</a>
focus-visible: is better for user experience because it doesn't show focus rings when clicking with a mouse, but still provides them for keyboard users.
Group and Peer Modifiers
Group Hover
The group and group-hover: utilities allow you to style child elements based on the parent's hover state:
Group Hover Examples
<!-- Card with group hover effects -->
<div class="group bg-white hover:bg-blue-50 p-6 rounded-lg shadow transition-all">
<h3 class="text-gray-800 group-hover:text-blue-600 text-xl font-bold">
Card Title
</h3>
<p class="text-gray-600 group-hover:text-gray-800 mt-2">
Hover over the card to see changes
</p>
<button class="bg-blue-500 group-hover:bg-blue-600 text-white px-4 py-2 rounded mt-4">
Learn More
</button>
</div>
<!-- Navigation with group hover -->
<a href="#" class="group flex items-center gap-3 p-3 rounded hover:bg-gray-100">
<svg class="w-6 h-6 text-gray-400 group-hover:text-blue-600">...</svg>
<span class="text-gray-700 group-hover:text-gray-900 group-hover:font-semibold">
Dashboard
</span>
<svg class="w-4 h-4 text-gray-400 group-hover:translate-x-1 transition-transform ml-auto">→</svg>
</a>
<!-- Image card with overlay -->
<div class="group relative overflow-hidden rounded-lg">
<img src="image.jpg" class="group-hover:scale-110 transition-transform duration-300" alt="Image">
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-50 transition-all">
<div class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<button class="bg-white text-gray-900 px-6 py-3 rounded-lg font-semibold">
View Details
</button>
</div>
</div>
</div>
Peer Modifiers
The peer and peer-*: utilities allow you to style an element based on the state of a sibling element:
Peer Modifier Examples
<!-- Floating label input -->
<div class="relative">
<input
type="text"
id="email"
class="peer w-full border-2 border-gray-300 focus:border-blue-500 px-4 py-3 rounded outline-none"
placeholder=" "
>
<label
for="email"
class="absolute left-4 top-3 text-gray-500 peer-focus:text-blue-500 peer-focus:text-sm peer-focus:-top-6 peer-focus:left-0 transition-all peer-[:not(:placeholder-shown)]:text-sm peer-[:not(:placeholder-shown)]:-top-6 peer-[:not(:placeholder-shown)]:left-0"
>
Email Address
</label>
</div>
<!-- Checkbox with peer styling -->
<div class="flex items-center gap-3">
<input type="checkbox" id="terms" class="peer w-5 h-5">
<label for="terms" class="text-gray-600 peer-checked:text-blue-600 peer-checked:font-semibold">
I agree to the terms and conditions
</label>
</div>
<!-- Radio button with custom styling -->
<div class="flex items-start gap-3">
<input type="radio" id="option1" name="option" class="peer sr-only">
<label for="option1" class="flex items-center gap-3 p-4 border-2 border-gray-300 peer-checked:border-blue-500 peer-checked:bg-blue-50 rounded-lg cursor-pointer">
<div class="w-6 h-6 rounded-full border-2 border-gray-300 peer-checked:border-blue-500 flex items-center justify-center">
<div class="w-3 h-3 rounded-full bg-blue-500 hidden peer-checked:block"></div>
</div>
<div>
<div class="font-semibold text-gray-900">Option 1</div>
<div class="text-sm text-gray-600">Description of option 1</div>
</div>
</label>
</div>
Structural Pseudo-Class Variants
First, Last, Odd, and Even
Tailwind provides variants for styling elements based on their position within a parent:
Structural Variants Examples
<!-- List with first and last styling -->
<ul class="divide-y divide-gray-200">
<li class="py-3 first:pt-0 last:pb-0">First Item</li>
<li class="py-3 first:pt-0 last:pb-0">Second Item</li>
<li class="py-3 first:pt-0 last:pb-0">Third Item</li>
<li class="py-3 first:pt-0 last:pb-0">Last Item</li>
</ul>
<!-- Table with odd/even row colors -->
<table class="w-full">
<tbody>
<tr class="odd:bg-white even:bg-gray-50">
<td class="px-4 py-3">Row 1</td>
</tr>
<tr class="odd:bg-white even:bg-gray-50">
<td class="px-4 py-3">Row 2</td>
</tr>
<tr class="odd:bg-white even:bg-gray-50">
<td class="px-4 py-3">Row 3</td>
</tr>
</tbody>
</table>
<!-- Navigation breadcrumbs -->
<nav class="flex items-center gap-2">
<a href="#" class="text-blue-600 hover:underline">Home</a>
<span class="text-gray-400">/</span>
<a href="#" class="text-blue-600 hover:underline">Products</a>
<span class="text-gray-400">/</span>
<span class="text-gray-600">Current Page</span>
</nav>
<!-- Grid with different first item -->
<div class="grid grid-cols-3 gap-4">
<div class="first:col-span-3 first:row-span-2 bg-gray-200 p-4 rounded">
Featured Item
</div>
<div class="bg-gray-100 p-4 rounded">Item 2</div>
<div class="bg-gray-100 p-4 rounded">Item 3</div>
<div class="bg-gray-100 p-4 rounded">Item 4</div>
</div>
Form State Variants
Disabled State
The disabled: modifier styles form elements when they are disabled:
Disabled State Examples
<!-- Disabled button -->
<button
disabled
class="bg-blue-500 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed text-white px-4 py-2 rounded"
>
Disabled Button
</button>
<!-- Disabled input -->
<input
type="text"
disabled
value="Disabled input"
class="border border-gray-300 disabled:bg-gray-100 disabled:text-gray-500 disabled:cursor-not-allowed px-4 py-2 rounded w-full"
>
<!-- Conditionally disabled -->
<button
class="bg-green-500 hover:bg-green-600 disabled:bg-gray-300 disabled:hover:bg-gray-300 disabled:cursor-not-allowed text-white px-6 py-3 rounded transition-colors"
disabled
>
Submit
</button>
Required State
The required: modifier styles form elements marked as required:
Required State Examples
<!-- Required input with indicator -->
<div class="relative">
<input
type="text"
required
class="border-2 border-gray-300 required:border-blue-300 focus:border-blue-500 px-4 py-2 rounded w-full"
>
<span class="absolute right-3 top-3 text-red-500 required:block hidden">*</span>
</div>
<!-- Form with required fields -->
<form class="space-y-4">
<div>
<label class="block text-gray-700 mb-2">
Name <span class="text-red-500">*</span>
</label>
<input
type="text"
required
class="border border-gray-300 required:border-l-4 required:border-l-blue-500 focus:border-blue-500 px-4 py-2 rounded w-full"
>
</div>
</form>
Placeholder State
The placeholder: modifier styles the placeholder text in input fields:
Placeholder State Examples
<!-- Custom placeholder styling -->
<input
type="text"
placeholder="Enter your email"
class="border border-gray-300 focus:border-blue-500 placeholder:text-gray-400 placeholder:italic px-4 py-2 rounded w-full"
>
<!-- Placeholder with custom color -->
<input
type="search"
placeholder="Search..."
class="bg-gray-100 placeholder:text-gray-500 px-4 py-2 rounded-full w-full"
>
<!-- Textarea with styled placeholder -->
<textarea
placeholder="Type your message here..."
class="border border-gray-300 placeholder:text-gray-400 placeholder:text-sm focus:border-blue-500 px-4 py-2 rounded w-full h-32"
></textarea>
File Input State
The file: modifier styles the file input button:
File Input Examples
<!-- Styled file input -->
<input
type="file"
class="block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-full file:border-0
file:text-sm file:font-semibold
file:bg-blue-50 file:text-blue-700
hover:file:bg-blue-100
file:cursor-pointer cursor-pointer"
>
<!-- Multiple file upload -->
<input
type="file"
multiple
class="block w-full text-sm text-gray-600
file:mr-4 file:py-3 file:px-6
file:rounded-lg file:border file:border-gray-300
file:text-sm file:font-medium
file:bg-white file:text-gray-700
hover:file:bg-gray-50
file:transition-colors"
>
Link State Variants
Visited State
The visited: modifier styles links that have been visited:
Visited State Examples
<!-- Links with visited state -->
<a href="#page1" class="text-blue-600 visited:text-purple-600 hover:underline">
Link 1 (changes color when visited)
</a>
<a href="#page2" class="text-blue-600 visited:text-gray-600 visited:line-through">
Link 2 (shows as read)
</a>
<!-- Article links -->
<ul class="space-y-2">
<li>
<a href="#article1" class="text-blue-600 visited:text-purple-700 visited:opacity-75 hover:underline">
Article 1 - Introduction to Tailwind
</a>
</li>
<li>
<a href="#article2" class="text-blue-600 visited:text-purple-700 visited:opacity-75 hover:underline">
Article 2 - Advanced Techniques
</a>
</li>
</ul>
Stacking State Variants
You can combine multiple state variants to create complex interactions:
Combined State Examples
<!-- Button with all states -->
<button class="
bg-blue-500
hover:bg-blue-600
active:bg-blue-700
focus:outline-none
focus:ring-4
focus:ring-blue-300
disabled:bg-gray-300
disabled:cursor-not-allowed
disabled:hover:bg-gray-300
text-white
font-semibold
px-6
py-3
rounded-lg
transition-all
">
Interactive Button
</button>
<!-- Card with group and individual states -->
<div class="group relative bg-white hover:shadow-2xl transition-shadow rounded-lg overflow-hidden">
<img
src="image.jpg"
class="w-full group-hover:scale-105 transition-transform duration-300"
alt="Card"
>
<div class="p-6">
<h3 class="text-xl font-bold text-gray-900 group-hover:text-blue-600 transition-colors">
Card Title
</h3>
<p class="text-gray-600 mt-2 group-hover:text-gray-800">
Card description text
</p>
<button class="
mt-4
bg-blue-500
hover:bg-blue-600
active:scale-95
focus:ring-4
focus:ring-blue-300
text-white
px-4
py-2
rounded
transition-all
">
Learn More
</button>
</div>
</div>
<!-- Form input with multiple states -->
<input
type="email"
required
placeholder="Enter email"
class="
w-full
border-2
border-gray-300
focus:border-blue-500
focus:ring-2
focus:ring-blue-200
invalid:border-red-500
invalid:focus:ring-red-200
disabled:bg-gray-100
disabled:cursor-not-allowed
placeholder:text-gray-400
placeholder:italic
px-4
py-3
rounded-lg
outline-none
transition-all
"
>
hover:bg-blue-600 focus:bg-blue-700 active:bg-blue-800
Exercise 1: Interactive Navigation Menu
Create a sidebar navigation menu with the following features:
- Use group hover to highlight the entire item when hovering
- Change icon color on group hover
- Add a sliding indicator that appears on hover
- Style the active/current page differently
- Include focus states for keyboard navigation
Exercise 2: Advanced Form with State Variants
Build a registration form that includes:
- Floating labels that move when input is focused or has content
- Custom styled checkboxes using peer modifiers
- Different border colors for required, focused, and invalid states
- Disabled submit button until form is valid
- Styled file upload button
Exercise 3: Product Card Grid
Create a responsive product card grid where each card:
- Scales up slightly on hover with smooth transition
- Shows an overlay with "Quick View" button on hover
- Has an "Add to Cart" button that changes on hover, focus, and active states
- Displays a "Sale" badge on the first item only
- Shows visited links in a different color
Summary
In this lesson, you've learned about Tailwind's powerful state variant system:
- Basic interaction states: hover, focus, active, visited
- Advanced focus variants: focus-within, focus-visible
- Group and peer modifiers: styling based on parent or sibling state
- Structural variants: first, last, odd, even
- Form states: disabled, required, placeholder, file input
- Stacking variants: combining multiple states for complex interactions
State variants are essential for creating interactive and accessible user interfaces. They allow you to add sophisticated behaviors without writing custom CSS, making your development process faster and more maintainable.