Tailwind CSS v4 Composing Variants for Complex State Combinations
In this tutorial, you will learn about Tailwind CSS v4 Composing Variants for Complex State Combinations. We cover key concepts, practical examples, and best practices to help you master this topic.
Tailwind CSS v4 variant composition allows combining multiple variants into unified prefixes, enabling complex state patterns like dark-mode-hover, desktop-only-focus, or mobile-active.
What You'll Learn
You will learn how to compose variants using the @variant directive with CSS nesting, create semantic variant names for common patterns, and manage specificity in composed variants.
Why It Matters
Composed variants reduce repetition. DodaTech's components use composed variants like dark-hover: and mobile-focus: instead of repeating dark:hover: everywhere.
Real-World Use
Durga Antivirus Pro uses composed dark-hover: and mobile-active: variants across its dashboard, reducing class string length by 30 percent and improving readability.
flowchart LR
A[Variants v4] --> B[Composition]
B --> C[Nesting]
B --> D[Semantic Names]
B --> E[Specificity]
B --> F[Reusability]
style B fill:#38bdf8,stroke:#0284c7,color:#fff
style C fill:#22c55e,stroke:#16a34a,color:#fff
Basic Composition
@import "tailwindcss";
/* Composing hover and dark into one variant */
@variant dark-hover (@media (prefers-color-scheme: dark) {
&:hover;
});
/* Composing focus and visible */
@variant focus-visible-dark (@media (prefers-color-scheme: dark) {
&:focus-visible;
});
<div class="bg-white dark-hover:bg-gray-800 p-4 rounded-lg">
Background changes only in dark mode on hover.
</div>
<button class="focus-visible-dark:ring-blue-300 px-4 py-2 rounded">
Focus ring changes in dark mode.
</button>
Expected output: dark-hover: applies the hover style only when both dark mode and hover state are active. The composed variant is a single clean prefix.
Multi-Condition Composition
/* Variant that requires all conditions */
@variant desktop-hover-active (@media (min-width: 768px) {
&:hover,
&:active;
});
/* Variant that matches any condition */
@variant mobile-or-dark (@media (max-width: 767px), (prefers-color-scheme: dark));
/* Print or screen-reader */
@variant print-or-sr (@media print, (prefers-reduced-motion: reduce));
<button class="
bg-blue-600 text-white px-4 py-2 rounded
desktop-hover-active:bg-blue-800
">
Only darkens on desktop hover or active.
</button>
<div class="mobile-or-dark:text-sm">
Smaller text on mobile OR in dark mode.
</div>
Expected output: Complex conditions bundled into single semantic variant prefixes. Desktop-hover-active requires both conditions. Mobile-or-dark matches either.
Semantic Variant Names
/* Semantic naming for better readability */
@variant card-hover (&:hover) {
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
@variant btn-press (&:active) {
transform: scale(0.97);
}
@variant nav-active (&[data-active="true"]) {
border-bottom-color: var(--color-brand-500);
color: var(--color-brand-600);
}
<button class="bg-white card-hover:shadow-lg btn-press:scale-95 px-4 py-2 rounded-lg">
Semantic variant names describe the visual effect.
</button>
Expected output: Semantic variant names (card-hover:, btn-press:, nav-active:) make HTML more readable and self-documenting.
Chaining Composed Variants
/* Build complex variants from simpler ones */
@variant dark (@media (prefers-color-scheme: dark));
@variant hover (&:hover);
@variant md (@media (min-width: 768px));
/* Compose from existing variants */
@variant dark-hover (@variant(dark) { @variant(hover) { & } });
@variant md-dark-hover (@variant(md) { @variant(dark) { @variant(hover) { & } } });
<div class="
bg-white
dark-hover:bg-gray-800
md-dark-hover:bg-gray-700
p-4 rounded-lg
">
Three levels of variant composition: dark-hover, then md-dark-hover.
</div>
Expected output: Variants can be built from other composed variants, creating a hierarchy of increasingly specific conditions.
Composing with @apply
@variant card-hover (&:hover);
.btn-base {
@apply px-4 py-2 rounded-lg font-medium transition-all;
}
.btn-primary {
@apply btn-base bg-brand-500 text-white card-hover:bg-brand-600 card-hover:shadow-lg;
}
<button class="btn-primary">Composed variant in @apply</button>
Expected output: Composed variants work inside @apply, enabling component classes that include complex state patterns.
Common Mistakes
1. Over-Composing Variants
Creating too many composed variants (dark-lg-hover-focus-active) hurts readability. Keep compositions to 2-3 conditions.
2. Not Testing Edge Cases
A composed variant like desktop-hover may fail on touch devices where hover is not supported. Test with actual devices.
3. Specificity Conflicts
Composed variants can create specificity wars. Use :where() in your composition to keep specificity low.
4. Semantic Names That Mislead
A variant named primary: should clearly communicate what it does. Avoid vague names like special: or custom:.
5. Forgetting Rebuild After @variant Changes
Like all @variant definitions, composed variants need a CSS rebuild. Verify builds after adding new compositions.
Practice Questions
How do you compose hover and dark into one variant?
@variant dark-hover (@media (prefers-color-scheme: dark) { &:hover; }).Can a composed variant match multiple conditions? Yes. Use commas for OR logic:
@variant mobile-or-dark (@media (max-width: 767px), (prefers-color-scheme: dark)).What is the advantage of semantic variant names? They make HTML more readable and self-documenting:
card-hover:instead ofhover:.How do you chain three conditions in a variant? Nest variants:
@variant md-dark-hover (@variant(md) { @variant(dark) { @variant(hover) { & } } }).Can composed variants be used with @apply? Yes. Composed variants work inside @apply just like any other variant.
Challenge
Create a composed variant system for a card component: dark-hover (dark mode + hover), mobile-focus (mobile + focus), desktop-active (desktop + active), and print-dark (print + dark mode).
FAQ
Mini Project
Build a component library with composed variants: 3 card variants with dark-hover, mobile-stack, and print-simple. 3 button variants with btn-press, dark-focus, and desktop-hover-shadow.
What's Next
Now master Custom Variants for advanced selector patterns. Then explore Dark Mode in v4 for the new dark mode approach.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro