Tailwind CSS Pseudo-Classes — First, Last, Odd, Even, and Empty
In this tutorial, you will learn about Tailwind CSS Pseudo. We cover key concepts, practical examples, and best practices to help you master this topic.
Tailwind CSS pseudo-class variants style elements based on structural position (first:, last:, odd:, even:, only:), content state (empty:), and element state (required:, invalid:, read-only:, placeholder-shown:).
What You'll Learn
You will learn how to use structural pseudo-class variants for lists and grids, form state variants for validation, and content-based variants for dynamic styling.
Why It Matters
Pseudo-class variants eliminate custom CSS for common patterns like alternating row colors, first/last element spacing, and form validation states -- all inline in HTML.
Real-World Use
Doda Browser uses odd:bg-gray-50 for alternating table rows, last:border-b-0 for list items, and empty:hidden for dynamic content containers that should collapse when empty.
flowchart LR
A[Variants] --> B[Pseudo-Classes]
B --> C[Positional]
B --> D[Form States]
B --> E[Content]
B --> F[Combined]
style B fill:#38bdf8,stroke:#0284c7,color:#fff
style C fill:#22c55e,stroke:#16a34a,color:#fff
Structural Pseudo-Classes
<div class="max-w-md mx-auto p-4">
<h3 class="font-bold mb-2">List with first/last styling:</h3>
<ul class="divide-y divide-gray-200 border rounded-lg">
<li class="px-4 py-3 first:rounded-t-lg last:rounded-b-lg first:bg-blue-50 last:bg-green-50">
First item (rounded top + blue bg)
</li>
<li class="px-4 py-3">Second item</li>
<li class="px-4 py-3">Third item</li>
<li class="px-4 py-3 last:rounded-b-lg last:bg-green-50">
Last item (rounded bottom + green bg)
</li>
</ul>
<h3 class="font-bold mt-6 mb-2">Odd/Even row colors:</h3>
<div class="border rounded-lg overflow-hidden">
<div class="px-4 py-3 even:bg-gray-50">Row 1 (odd)</div>
<div class="px-4 py-3 even:bg-gray-50">Row 2 (even - gray bg)</div>
<div class="px-4 py-3 even:bg-gray-50">Row 3 (odd)</div>
<div class="px-4 py-3 even:bg-gray-50">Row 4 (even - gray bg)</div>
</div>
</div>
Expected output: First and last items have rounded corners and background colors. Even-numbered rows have alternate background.
Form State Pseudo-Classes
<form class="max-w-md mx-auto p-6 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Email *</label>
<input type="email" required placeholder="your@email.com"
class="w-full px-4 py-2 border rounded-lg
required:border-blue-500
valid:border-green-500 valid:bg-green-50
invalid:border-red-500 invalid:bg-red-50
focus:ring-2 focus:ring-blue-500 focus:border-transparent
placeholder-shown:border-gray-300 placeholder-shown:bg-white
transition-colors">
<p class="text-xs text-gray-500 mt-1">Try typing an invalid email to see validation styles.</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Age *</label>
<input type="number" required min="18" max="120" placeholder="18"
class="w-full px-4 py-2 border rounded-lg
valid:border-green-500
invalid:border-red-500
focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Bio</label>
<textarea placeholder="Optional bio..."
class="w-full px-4 py-2 border border-gray-300 rounded-lg
placeholder-shown:text-gray-400
focus:ring-2 focus:ring-blue-500"></textarea>
</div>
<button class="w-full bg-blue-600 text-white py-2 rounded-lg font-medium
disabled:opacity-50 disabled:cursor-not-allowed">
Submit
</button>
</form>
Expected output: Form inputs show different border and background colors based on validation state (valid/invalid), required state, and placeholder visibility.
Empty State Variant
<div class="max-w-md mx-auto p-4 space-y-4">
<div class="p-4 border rounded-lg empty:hidden bg-blue-50">
This div has content (visible)
</div>
<div class="p-4 border rounded-lg empty:hidden bg-green-50">
</div>
<p class="text-sm text-gray-500">Empty div above is hidden by empty:hidden</p>
<div class="empty:p-4 empty:bg-gray-50 empty:border-2 empty:border-dashed empty:border-gray-300 empty:rounded-lg empty:before:content-['No_items_found'] empty:before:text-gray-400">
</div>
<p class="text-sm text-gray-500">Empty div with placeholder styling via empty: variant</p>
</div>
Expected output: Empty elements with empty:hidden collapse and are invisible. Empty elements with empty:p-4 show placeholder styling.
Only and Nth-Child Variants
<div class="max-w-md mx-auto p-4 space-y-4">
<!-- only: styles an element that is the only child -->
<div>
<h3 class="font-bold mb-2">only: (single child):</h3>
<div class="flex gap-2">
<span class="only:bg-purple-100 only:px-4 only:py-2 only:rounded-lg only:font-bold bg-gray-100 px-4 py-2 rounded">
Single item
</span>
</div>
</div>
<!-- Multiple children (only: does not apply) -->
<div>
<h3 class="font-bold mb-2">Multiple children (only: not applied):</h3>
<div class="flex gap-2">
<span class="only:bg-purple-100 bg-gray-100 px-4 py-2 rounded">Item 1</span>
<span class="only:bg-purple-100 bg-gray-100 px-4 py-2 rounded">Item 2</span>
</div>
</div>
</div>
Expected output: The single child element has purple background (only: variant applies). The multiple children do not get the only: styling.
Combining Pseudo-Classes
<div class="max-w-md mx-auto p-4">
<ul class="divide-y divide-gray-200 border rounded-lg overflow-hidden">
<li class="px-4 py-3 first:bg-blue-50 odd:bg-gray-50/50 last:border-b-0 hover:bg-gray-100 transition-colors">
Item 1 (first + odd)
</li>
<li class="px-4 py-3 odd:bg-gray-50/50 hover:bg-gray-100 transition-colors">
Item 2 (even)
</li>
<li class="px-4 py-3 odd:bg-gray-50/50 hover:bg-gray-100 transition-colors">
Item 3 (odd)
</li>
<li class="px-4 py-3 odd:bg-gray-50/50 last:border-b-0 hover:bg-gray-100 transition-colors">
Item 4 (last + even)
</li>
</ul>
</div>
Expected output: A styled list combining first, odd, last, and hover pseudo-class variants for a polished interactive list.
Common Mistakes
1. Forgetting the Semicolon in @apply Chains
When pseudo-classes are combined via @apply, separate them with spaces: @apply odd:bg-gray-50 even:bg-white.
2. Using empty: on Elements With Whitespace
empty: matches elements with zero child nodes. Whitespace or a single space text node means the element is not empty.
3. Confusing first: and first-of-type:
first: matches the first child regardless of type. first-of-type: matches the first child of that element type.
4. Not Testing Form States in Browsers
Form validation pseudo-classes (valid, invalid) require actual form interaction to trigger. Test by submitting with invalid data.
5. Overusing Structural Variants
Not every list needs alternating colors. Use structural variants only when they improve readability or user experience.
Practice Questions
What variant targets every other element?
odd:for odd-indexed elements,even:for even-indexed elements.How do you style only the first child?
first:applies to the first child.first-of-type:applies to the first child of that tag name.What variant styles an element only when it is the sole child?
only:applies when the element has no siblings.How do you hide empty elements?
empty:hiddenorempty:hidden empty:invisible.What form validation variants are available? valid:, invalid:, required:, optional:, read-only:, read-write:, placeholder-shown:, default:, indeterminate:, checked:.
Challenge
Build a data table with: odd:row background colors, first: rounded top, last: rounded bottom, hover: row highlighting, empty: cell placeholders, and valid/invalid styling for inline editing.
FAQ
Mini Project
Build an interactive data table with: odd/even row colors, first/last rounded corners, hover highlight, empty row placeholders, valid/invalid inline edit fields, and responsive column hiding.
What's Next
Now master Animations and transitions for motion in Tailwind. Learn animate-* utilities, custom keyframes, and transition controls.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro