Introduction
Tailwind CSS arbitrary variants allow you to create custom styles that target specific elements in your HTML structure. This is particularly useful when you need to apply styles to child elements based on their type, class, or attributes without adding additional CSS classes or JavaScript.
While the official documentation covers the basics, it doesn't demonstrate how to handle more complex scenarios like targeting deeply nested children, combining multiple selectors, or working with custom attributes.
Use arbitrary variants sparingly as they can reduce code readability and maintainability.
Core Concepts
Understanding the & Symbol
The & character represents the current element (the one with the arbitrary variant class). Think of it as "this element" in your selector.
Canonical Syntax vs Arbitrary Variants
Tailwind CSS provides canonical variants for common combinator and pseudo-class patterns. Prefer these over arbitrary variants when they fit:
*:- Targets direct children (replaces[&>*])**:- Targets all descendants (replaces[&_*])has-[...]- Element has matching descendant (replaces[&:has(...)])not-[...]- Negates a selector (replaces[&:not(...)])nth-[N]- Matches:nth-child(N)(replaces[&:nth-child(N)])in-[...]- Matches when inside a parent (replaces[..._&])
Reach for arbitrary variants like [&>tag] or [&_.class] only when you need a structural filter the canonical variants can't express — for example, a direct child of a specific tag, or a specific descendant path.
Direct Children vs All Descendants
This is the most important distinction to understand:
*:or[&>...]- Targets only direct children (immediate descendants)**:or[&_...]- Targets all descendants at any nesting level
<div className="[&>p]:text-red-500 [&_span]:text-blue-500">
<p>Direct child - will be red</p>
<div>
<p>Nested child - won't be red (not direct)</p>
<span>Nested span - will be blue (all descendants)</span>
</div>
<span>Direct span - will be blue</span>
</div>Direct child - will be red
Nested child - won't be red (not direct)
Nested span - will be blue (all descendants)Targeting Children by Tag
When you need to target direct children by their tag, you can use the & selector followed by the tag name.
<div className="[&>p]:text-red-600">
<span>Black text</span>
<p>Red text</p>
</div>Red text
Targeting Children by Class
When you need to target direct children by their class, you can use the & selector with the class name prefixed by a dot.
This also works with className attributes in React components.
<div className="[&>.child2]:text-red-600">
<div className="child1">Black text</div>
<div className="child2">Red text</div>
</div>You can also add additional classes to the selector.
<div className="[&>div.child.foo]:text-red-600">
<div className="child">Black text</div>
<div className="child foo">Red text</div>
</div>Targeting Children by Data Attributes
When you need to target elements based on their data attributes, you can use the *: variant for direct children combined with the data-[attribute="value"] syntax.
This also works with other attributes, not just data-*, for example, role, aria-*, or custom attributes.
Target immediate children with specific data attributes using the *:data-[attribute="value"] pattern.
<div className="*:data-[type='special']:text-red-600">
<div data-type="normal">Black text</div>
<div data-type="special">Red text</div>
</div>You can also target nested elements with data attributes using the **: variant for all descendants.
<div className="**:data-[type='special']:text-red-600">
<div>
<div data-type="special">Red text</div>
</div>
</div>When you need to target elements based on both their attributes and classes, you can combine them in the selector.
<div className="[&_[data-type='a'].red]:text-red-500 [&_[data-type='a'].blue]:text-blue-500">
<p data-type="a" className="red">Red text</p>
<p data-type="a" className="blue">Blue text</p>
</div>Red text
Blue text
Targeting Nested Child Elements
When you need to target deeply nested child elements, you can use the & selector with a path that describes the hierarchy.
<div className="[&>div>div>span]:text-red-600">
<div>
<div>
<span>Deep Child</span>
</div>
</div>
</div>Conditional Styling Based on Parent Tag
When you need to style child elements based on the tag of their parent, you can use the in-[tag] variant to check if the element is inside a specific parent tag.
<p>
<span className="in-[p]:text-red-600">Styled if parent is p</span>
</p>
<div>
<span className="in-[p]:text-red-600">Styled if parent is p</span>
</div>Styled if parent is p
Conditional Styling Based on Parent Class
When you need to style child elements based on the class of their parent, you can use the in-[.classname] variant to check if the element is inside a parent with a specific class.
<div className="foo">
<div>
<div className="in-[.foo]:text-red-600">Child 1</div>
<div className="in-[.bar]:text-red-600">Child 2</div>
</div>
</div>Conditional Styling Based on Parent Attribute
When you need to style child elements based on a specific attribute of their parent, you can use the in-data-[attribute="value"] variant to check if the element is inside a parent with that data attribute.
<div data-type="special">
<p className="in-data-[type='special']:text-red-600">Styled if parent has data-type="special"</p>
</div>
<div>
<p className="in-data-[type='special']:text-red-600">Not styled</p>
</div>Styled if parent has data-type="special"
Not styled
Targeting by nth-child
You can use the & selector with :nth-child(n) to target specific children based on their position within the parent.
<div className="*:nth-[3]:text-red-600">
<p>One</p>
<p>Two</p>
<p>Three</p>
</div>One
Two
Three
To combine multiple nth-child selectors to target different children, stack a separate variant per match.
<div className="*:first:text-red-600 *:nth-[3]:text-red-600">
<p>One</p>
<p>Two</p>
<p>Three</p>
</div>One
Two
Three
Advanced Combinations
Target second child inside .foo only
When you want to target a specific child element inside a parent with a specific class, you can use the & selector with :nth-child(n).
<div className="[&>div.foo>*:nth-child(2)]:text-red-600">
<div className="foo">
<p>1</p>
<p>2</p>
<p>3</p>
</div>
<div className="bar">
<p>Not affected</p>
</div>
</div>1
2
3
Conditional nth-child with Exclusion
When you need to exclude certain elements from a universal styling rule, you can use the :not() pseudo-class.
<div className="*:not-[.excluded]:text-red-600">
<div>
<div>Not Excluded</div>
</div>
<div className="excluded">
<div>Excluded</div>
</div>
</div><div className="*:not-[[excluded]]:text-red-600">
<div>
<div>Not Excluded</div>
</div>
<div excluded>
<div>Excluded</div>
</div>
</div>Pseudo + Tag + Descendant + Modifier
When you need to style elements based on a combination of pseudo-classes, tags, and modifiers, compose the canonical has-[...] variant with an arbitrary descendant filter.
<div className="has-[span]:[&>p]:text-red-600">
<span>Trigger</span>
<p>Styled paragraph</p> <!-- Styled because parent has span -->
</div>
<div className="has-[span]:[&>p]:text-red-600">
<div>Trigger</div>
<p>Styled paragraph</p>
</div>Styled paragraph
Styled paragraph
Conclusion
Tailwind CSS arbitrary variants provide powerful tools for targeting child elements, styling based on parent context, and handling complex structures without the need for custom CSS or JavaScript. However, they should be used with care to maintain readability and code maintainability.
Cheat Sheet
| Selector | Use Case |
|---|---|
*: | Target all direct children |
**: | Target all descendants |
[&>tag] | Target direct child elements by tag |
[&_tag] | Target all descendant elements by tag |
[&>.class] | Target direct children by class |
[&_.class] | Target all descendants by class |
[&>div.class1.class2] | Target direct children with multiple classes |
*:data-[x="y"] | Target direct children with data attributes |
**:data-[x="y"] | Target any nested element with data attributes |
[&_[data-type='a'].class] | Combine attributes and classes |
[&>div>div>span] | Target deeply nested children with specific path |
in-[tag] | Style based on parent's tag |
in-[.parent] | Style based on parent's class |
in-data-[x="y"] | Style based on parent's attributes |
*:nth-[N] | Target the Nth direct child |
*:first / *:last / *:odd / *:even | Common position shortcuts |
*:not-[.excluded] | Exclude direct children by class |
*:not-[[excluded]] | Exclude direct children by attribute |
has-[selector] | Element has matching descendant (canonical for :has()) |
has-[span]:[&>p] | Style direct children when element contains a specific descendant |


