Tailwind CSS Variants — Responsive, State, and Custom Variants
In this tutorial, you will learn about Tailwind CSS Variants. We cover key concepts, practical examples, and best practices to help you master this topic.
Tailwind CSS variants are prefix modifiers that conditionally apply utility classes based on responsive breakpoints, interaction states, dark mode, group state, and custom conditions.
What You'll Learn
You will learn how variants work in Tailwind, available variant categories, how to combine multiple variants, and how to create custom variants for project-specific conditions.
Why It Matters
Variants eliminate conditional CSS. DodaTech uses variants for every interactive element -- hover for desktop, focus for keyboard, dark for theme, and md/lg for responsive adjustments.
Real-World Use
Durga Antivirus Pro uses combined responsive-state variants like md:hover:bg-gray-100 and dark:focus:ring-blue-400 throughout the dashboard for comprehensive interaction feedback.
flowchart LR
A[Directives] --> B[Variants]
B --> C[Responsive]
B --> D[State]
B --> E[Dark Mode]
B --> F[Group/Peer]
B --> G[Custom]
style B fill:#38bdf8,stroke:#0284c7,color:#fff
style C fill:#22c55e,stroke:#16a34a,color:#fff
Variant Categories
<div class="p-8 space-y-6">
<!-- Responsive variants -->
<div class="text-sm md:text-base lg:text-lg xl:text-xl">
Responsive: size changes at each breakpoint
</div>
<!-- State variants -->
<button class="bg-blue-600 text-white px-6 py-3 rounded-lg
hover:bg-blue-700 focus:ring-2 focus:ring-blue-500
active:bg-blue-800 disabled:opacity-50">
State variants
</button>
<!-- Dark mode -->
<div class="bg-white dark:bg-gray-800 p-4 rounded-lg">
<p class="text-gray-900 dark:text-white">Dark mode variant</p>
</div>
<!-- Group hover -->
<div class="group cursor-pointer border p-4 rounded-lg hover:shadow-md">
<h3 class="text-gray-900 group-hover:text-blue-600">Group hover title</h3>
</div>
</div>
Expected output: Elements responding to viewport size, hover, focus, dark mode, and parent hover -- all via variant prefixes.
Combining Multiple Variants
<button class="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium
hover:bg-blue-700
dark:bg-blue-500 dark:hover:bg-blue-400
md:text-lg
md:hover:shadow-lg
dark:focus:ring-blue-300
transition-all duration-200">
Combined Variants Button
</button>
Expected output: A button that changes background on hover, adapts for dark mode, is larger on desktop, has a shadow on desktop hover, and a custom focus ring in dark mode -- all with combined variants.
Using Variants with @apply
/* custom.css */
.btn {
@apply bg-blue-600 text-white font-medium py-2 px-4 rounded-lg
hover:bg-blue-700
focus:ring-2 focus:ring-blue-500
dark:bg-blue-500 dark:hover:bg-blue-400
md:text-base lg:text-lg
transition-colors;
}
Expected output: A .btn class that includes variant-based styles via @apply, reducing repetition when reusing the same variant combinations.
Variant Order
<div class="p-4 bg-gray-100 hover:bg-blue-100 lg:hover:bg-purple-100 dark:lg:hover:bg-purple-800">
<!-- Variant order: responsive > state > custom -->
<!-- Applied in order: base -> hover -> lg -> lg:hover -> dark -> dark:lg -> dark:lg:hover -->
<p>Hover colors change based on viewport and theme.</p>
</div>
Expected output: The background color cascades through variant layers. Dark mode overrides light. Responsive overrides base. State overrides responsive.
Custom Variants
// tailwind.config.js
const plugin = require('tailwindcss/plugin');
module.exports = {
plugins: [
plugin(function({ addVariant }) {
// Open/closed state for details/summary elements
addVariant('open', 'details[open] &');
// Loading state
addVariant('loading', '&[data-loading="true"]');
// Print media
addVariant('print', '@media print');
// Landscape orientation
addVariant('landscape', '@media (orientation: landscape)');
}),
],
}
<details class="open:bg-blue-50 open:p-4 open:rounded-lg">
<summary class="cursor-pointer font-medium">Click to expand</summary>
<p class="mt-2 text-gray-600">This content is revealed on open.</p>
</details>
<div data-loading="true" class="loading:opacity-50 loading:cursor-wait">
Content with loading state
</div>
<div class="landscape:flex landscape:gap-4">
<div class="bg-blue-100 p-4">Side by side in landscape</div>
<div class="bg-green-100 p-4">Side by side in landscape</div>
</div>
Expected output: Custom variants for open state, loading state, landscape orientation, and print media are available as prefix modifiers.
Peer Variants
<div class="space-y-4">
<!-- Peer: style one element based on sibling state -->
<div class="flex items-center gap-3">
<input type="checkbox" class="peer w-4 h-4 text-blue-600">
<label class="text-gray-700 peer-checked:text-blue-600 peer-checked:font-medium">
Check me to see the peer effect
</label>
</div>
<!-- Peer with hover -->
<div class="flex gap-4">
<div class="peer/hover w-24 h-24 bg-blue-100 rounded-lg flex items-center justify-center cursor-pointer hover:bg-blue-200">
Hover
</div>
<div class="peer-hover/hover:bg-blue-50 w-24 h-24 bg-gray-100 rounded-lg flex items-center justify-center">
Reacts
</div>
</div>
</div>
Expected output: The label text changes color when the checkbox is checked. The second box changes background when the first box is hovered.
Common Mistakes
1. Too Many Combined Variants
dark:md:hover:focus:bg-red-100 is hard to read and maintain. Keep variant chains to 2-3 prefixes maximum.
2. Forgetting Base Style
:hover only works if there is a base style to override. hover:bg-blue-700 needs bg-blue-600 as the base.
3. Wrong Variant Order
Variant order matters: hover:md:bg-red is different from md:hover:bg-red. The leftmost variant has higher specificity.
4. Not Using Peer When Needed
Peer variants style one element based on another's state. Without peer, you need JavaScript for cross-element state styling.
5. Overriding Dark Mode Variants
Dark mode variants must be explicitly added. Without dark:bg-gray-800, the element keeps its light background in dark mode.
Practice Questions
What is the syntax for variants?
{variant}:utility— e.g.,hover:bg-blue-700,md:text-lg,dark:text-white.How many variants can be combined? Any number, but 2-3 is the practical limit for readability.
What does the peer variant do? Styles an element based on the state of a preceding sibling with the
peerclass.What is variant order precedence? Rightmost variant has highest specificity:
dark:hover:bg-blue-400applies in dark mode on hover.Can you create variants for custom data attributes? Yes. Use
addVariantin a plugin:addVariant('data-active', '&[data-active="true"]').
Challenge
Create a form with: peer-checked styling for toggle switches, group-hover for card reveal effects, custom data-loading variant for submit buttons, and combined md:hover variants for desktop-only interactions.
FAQ
Mini Project
Build an interactive dashboard widget with: hover for row highlighting, peer-checked for toggle switches, group-hover for card actions, focus for keyboard inputs, dark for all elements, and responsive for layout changes.
What's Next
Now master Pseudo-Classes for styling based on element state and position. Then explore Animations for motion and transitions.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro