Skip to content

Tailwind Arbitrary Variants

Arbitrary variants extend the same square-bracket escape hatch to selectors and states, letting you apply a utility conditionally based on nearly any CSS selector, not just Tailwind's built-in variant list. This lesson covers the syntax and practical use cases.

What Are Arbitrary Variants?

Just as arbitrary values let you write a raw CSS value, arbitrary variants let you write a raw CSS selector or condition in square brackets before a utility, applying that utility only when the selector matches.

This covers cases Tailwind's built-in variants (hover:, focus:, md:, etc.) don't handle out of the box, like targeting a specific data attribute, a sibling combinator, or an unusual pseudo-class.

<ul>
  <li class="[&:nth-child(3)]:font-bold">Third item is bold</li>
</ul>

[&:nth-child(3)] targets the exact CSS selector inside the brackets, applying font-bold only to the third list item.

Arbitrary Variant Syntax

class="[&:selector]:utility"
class="[&[data-attr='value']]:utility"
class="[&>*]:utility"
  • The & represents the element itself within the bracketed selector.
  • Combine with pseudo-classes, attribute selectors, or child/sibling combinators as needed.
  • Works alongside responsive and state prefixes, like md:[&:first-child]:mt-0.
  • For simple, common needs, check if a built-in variant already covers it before reaching for this syntax.

Arbitrary Variants Cheatsheet

Common arbitrary variant patterns for custom selectors.

Example Matches
[&:nth-child(odd)]:bg-slate-50 Every odd-numbered child
[&:first-child]:mt-0 Only the first child element
[&>*]:mx-2 Every direct child of the element
[&[data-state='open']]:block Elements with a specific data attribute value
[&::placeholder]:text-slate-400 The placeholder pseudo-element
[&_p]:text-sm Any descendant <p> element
has-[:checked]:bg-blue-50 Built-in has-* variant for a checked descendant
group-[.is-open]:rotate-180 A group ancestor matching a specific class

Styling Descendant Elements

The underscore-based descendant selector syntax lets you style elements you don't directly control, common when working with rendered Markdown or third-party component output where you can't add classes to inner elements.

<div class="[&_a]:text-blue-600 [&_a]:underline">
  <!-- Any nested <a> tag gets styled, even without its own class -->
  <p>Check out our <a href="/docs">documentation</a>.</p>
</div>

Targeting Pseudo-Elements

Some pseudo-elements, like ::placeholder or ::-webkit-scrollbar, aren't fully covered by Tailwind's built-in variants. Arbitrary variants let you target them directly.

<input
  placeholder="Search..."
  class="[&::placeholder]:text-slate-400 [&::placeholder]:italic"
/>

Combining Arbitrary and Built-In Variants

Arbitrary variants can be chained with responsive and state prefixes in any order, giving you full flexibility for genuinely unusual conditions while still benefiting from Tailwind's existing variant system.

<li class="md:[&:nth-child(3)]:font-bold">
  Bold only at md breakpoint and up, and only for the third item.
</li>

Common Mistakes

  • Reaching for an arbitrary variant when a built-in one (like first:, last:, or even:) already covers the exact same case.
  • Forgetting the & placeholder inside the brackets, which represents the element the class is applied to.
  • Writing overly complex selectors that would be clearer as a small custom plugin variant instead.
  • Not testing arbitrary variant selectors carefully, since a typo inside the brackets fails silently with no build error in some setups.
  • Overusing descendant selectors ([&_p]) as a substitute for properly structuring markup with classes where possible.

Key Takeaways

  • Arbitrary variants apply a utility based on a raw CSS selector written in square brackets.
  • The & symbol inside the brackets represents the current element.
  • They're useful for pseudo-elements, attribute selectors, and descendant styling Tailwind doesn't cover by default.
  • Arbitrary variants can be combined with responsive and state prefixes in any order.
  • Check for an existing built-in variant first; many common cases (first:, odd:, has-*:) are already covered.

Pro Tip

Before writing an arbitrary variant, search the Tailwind documentation for your exact need, variants like first:, odd:, has-*:, and group-*: cover far more cases than most developers initially expect.