Skip to content

Tailwind CSS v4 Composing Variants for Complex State Combinations

DodaTech Updated 2026-06-28 4 min read

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

  1. How do you compose hover and dark into one variant? @variant dark-hover (@media (prefers-color-scheme: dark) { &:hover; }).

  2. 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)).

  3. What is the advantage of semantic variant names? They make HTML more readable and self-documenting: card-hover: instead of hover:.

  4. How do you chain three conditions in a variant? Nest variants: @variant md-dark-hover (@variant(md) { @variant(dark) { @variant(hover) { & } } }).

  5. 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

Do composed variants affect performance?

Negligible. They generate the same CSS as manually writing the combined selectors.

Can I share composed variants across projects?

Yes. Extract the @variant definitions into a shared CSS file and @import it.

What is the maximum readable composition depth?

2-3 conditions per variant name. Beyond that, split into two variants.

Can composed variants use CSS custom properties?

Yes. Variants can reference CSS variables via var() in their definitions.

How do I debug composed variants?

Inspect the compiled CSS to see the generated selectors. Verify specificity and conditions.

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