Skip to main content
Tailwind CSS provides two directives for creating custom variants: @variant for applying existing variants within custom utilities and nested CSS, and @custom-variant for defining entirely new variant modifiers.

@variant Directive

The @variant directive applies an existing variant to a block of CSS, creating the appropriate selector transformations.

Syntax

@variant variant-name {
  /* CSS that will be wrapped with the variant */
}

Basic Usage

Use @variant to apply conditional styling within custom utilities:
@utility btn {
  padding: 0.5rem 1rem;
  background: blue;

  @variant hover {
    background: darkblue;
  }

  @variant focus {
    outline: 2px solid blue;
  }
}
This generates:
.btn {
  padding: 0.5rem 1rem;
  background: blue;
}

.btn:hover {
  background: darkblue;
}

.btn:focus {
  outline: 2px solid blue;
}

Using with @custom-variant

You can use @variant with custom variants defined via @custom-variant:
@custom-variant hocus (&:hover, &:focus);

@utility interactive {
  color: blue;

  @variant hocus {
    color: darkblue;
    text-decoration: underline;
  }
}

Nested @variant

@variant can be used within custom utilities or @custom-variant bodies:
@custom-variant complex {
  &:hover {
    @variant focus {
      @slot;
    }
  }
}

@custom-variant Directive

The @custom-variant directive defines new variant modifiers that can be used throughout your CSS.

Syntax

@custom-variant name (selector1, selector2);

Creating Custom Variants

Body-less Selector Variant

Define simple variants with comma-separated selectors:
@custom-variant hocus (&:hover, &:focus);
Use it with utilities:
<div class="hocus:underline">Underlined on hover or focus</div>
Generated CSS:
.hocus\:underline:hover,
.hocus\:underline:focus {
  text-decoration-line: underline;
}

Multiple Selectors

Combine different selector types:
@custom-variant interactive (&:hover, &:focus, &:active);

At-Rule Variants

Create variants with media queries or other at-rules:
@custom-variant any-hover (@media (any-hover: hover));
<div class="any-hover:hover:underline">Only underlined on hover-capable devices</div>
Generated CSS:
@media (any-hover: hover) {
  @media (hover: hover) {
    .any-hover\:hover\:underline:hover {
      text-decoration-line: underline;
    }
  }
}

Body Syntax with @slot

For more complex variants, use the body syntax with @slot as a placeholder:
@custom-variant hocus {
  &:hover {
    @slot;
  }

  &:focus {
    @slot;
  }
}
The @slot directive marks where the utility CSS should be inserted.

Nested Structure

Create complex selector structures:
@custom-variant card-hover {
  &:hover {
    & > .card-content {
      @slot;
    }
  }
}
<div class="card-hover:opacity-100">
  <div class="card-content">Fades in on card hover</div>
</div>

Variant Naming Rules

Valid Characters
string
Variant names must:
  • Start with a lowercase letter or number
  • Contain only alphanumeric characters, hyphens, or underscores
  • Not end with a hyphen or underscore
  • Match the pattern: /^@?[a-z0-9][a-zA-Z0-9_-]*(?<![_-])$/
@custom-variant hocus (&:hover);
@custom-variant my-variant (&:hover);
@custom-variant variant_1 (&:hover);
@custom-variant -invalid (&:hover);    /* starts with hyphen */
❌ @custom-variant _invalid (&:hover);    /* starts with underscore */
❌ @custom-variant Invalid (&:hover);     /* starts uppercase */
❌ @custom-variant variant- (&:hover);    /* ends with hyphen */

Compound Variants

Use custom variants with compound modifiers like group and peer:
@custom-variant hocus (&:hover, &:focus);
<div class="group">
  <div class="group-hocus:flex">Shows on group hover or focus</div>
</div>

Variant Dependencies

Custom variants can use other custom variants:
@custom-variant interaction {
  @variant hocus {
    @slot;
  }
}

@custom-variant hocus (&:hover, &:focus);
Circular dependencies between variants will cause an error.
/* ❌ This will error */
@custom-variant a {
  @variant b {
    @slot;
  }
}

@custom-variant b {
  @variant a {
    @slot;
  }
}
Error message:
Circular dependency detected in custom variants:
@custom-variant a { @variant b { … } }
@custom-variant b { @variant a { … } } /* ← */

Restrictions

Top-level Only

@custom-variant must be defined at the top level and cannot be nested.
/* ❌ Invalid */
.container {
  @custom-variant nested (&:hover);
}

/* ✅ Valid */
@custom-variant top-level (&:hover);

Selector or Body Required

Body-less variants must have a selector. Variants with a body must have @slot.
/* ❌ Invalid - no selector or body */
@custom-variant empty;

/* ❌ Invalid - empty body */
@custom-variant empty-body {
}

/* ✅ Valid - has selector */
@custom-variant valid (&:hover);

/* ✅ Valid - has body with @slot */
@custom-variant valid-body {
  &:hover {
    @slot;
  }
}

Cannot Mix Selector and Body

You cannot use both a selector parameter and a body.
/* ❌ Invalid - has both */
@custom-variant invalid (&:hover) {
  @slot;
}

/* ✅ Valid - selector only */
@custom-variant valid (&:hover);

/* ✅ Valid - body only */
@custom-variant valid {
  &:hover {
    @slot;
  }
}

Valid Selectors

Selector lists cannot be empty or contain empty selectors.
/* ❌ Invalid - empty selector list */
@custom-variant invalid ();

/* ❌ Invalid - has empty selector */
@custom-variant invalid (.foo, , .bar);

/* ✅ Valid */
@custom-variant valid (.foo, .bar);

Compound Capabilities

Custom variants can specify what types of selectors they work with:
  • StyleRules: Works with CSS selectors (:hover, .class, etc.)
  • AtRules: Works with at-rules (@media, @supports, etc.)
  • Never: Cannot be used in compound variants
From the source code (variants.ts:319-346):
export function compoundsForSelectors(selectors: string[]) {
  let compounds = Compounds.Never

  for (let sel of selectors) {
    if (sel[0] === '@') {
      // Non-conditional at-rules can't compound
      if (!sel.startsWith('@media') &&
          !sel.startsWith('@supports') &&
          !sel.startsWith('@container')) {
        return Compounds.Never
      }
      compounds |= Compounds.AtRules
      continue
    }

    // Pseudo-elements can't compound
    if (sel.includes('::')) {
      return Compounds.Never
    }

    compounds |= Compounds.StyleRules
  }

  return compounds
}

Output

The @custom-variant directive is removed from the final CSS. Only the transformed utility classes are output:
/* Input */
@custom-variant hocus (&:hover, &:focus);

@tailwind utilities;
<!-- Usage -->
<div class="hocus:underline"></div>
/* Output */
.hocus\:underline:hover,
.hocus\:underline:focus {
  text-decoration-line: underline;
}

Build docs developers (and LLMs) love