Tailwind CSS v4 Custom Variants — Advanced Selector Patterns
In this tutorial, you will learn about Tailwind CSS v4 Custom Variants. We cover key concepts, practical examples, and best practices to help you master this topic.
Tailwind CSS v4 custom variants using @variant support advanced selector patterns including nth-child, attribute selectors, parent state, sibling state, media conditions, and container queries.
What You'll Learn
You will learn how to create advanced custom variants for nth-child patterns, attribute-based states, parent and sibling interactions, media queries, and container query conditions.
Why It Matters
Advanced custom variants eliminate JavaScript for complex conditional styling. DodaTech uses custom variants for table striping, form validation states, and responsive visibility patterns.
Real-World Use
Doda Browser's extension uses custom variants for nth-child alternating styles, data-active navigation highlighting, and container-query-based responsive layouts -- all without JavaScript.
flowchart LR
A[Composing Variants] --> B[Custom Variants]
B --> C[Nth-Child]
B --> D[Attributes]
B --> E[Parent/Sibling]
B --> F[Media/Container]
style B fill:#38bdf8,stroke:#0284c7,color:#fff
style C fill:#22c55e,stroke:#16a34a,color:#fff
Nth-Child Variants
@import "tailwindcss";
/* Structural variants */
@variant even (&:nth-child(even));
@variant odd (&:nth-child(odd));
@variant first (&:first-child);
@variant last (&:last-child);
@variant only (&:only-child);
/* Custom nth patterns */
@variant nth-3 (&:nth-child(3));
@variant nth-3n (&:nth-child(3n));
@variant nth-3n-plus-1 (&:nth-child(3n+1));
@variant nth-last-2 (&:nth-last-child(2));
<div class="space-y-2">
<div class="even:bg-gray-50 nth-3:bg-blue-50 nth-3n:border-l-4 nth-3n:border-blue-500 p-3">
Row 1
</div>
<div class="even:bg-gray-50 nth-3:bg-blue-50 nth-3n:border-l-4 nth-3n:border-blue-500 p-3">
Row 2
</div>
<div class="even:bg-gray-50 nth-3:bg-blue-50 nth-3n:border-l-4 nth-3n:border-blue-500 p-3">
Row 3 (nth-3 applies)
</div>
<div class="even:bg-gray-50 nth-3:bg-blue-50 nth-3n:border-l-4 nth-3n:border-blue-500 p-3">
Row 4
</div>
<div class="even:bg-gray-50 nth-3:bg-blue-50 nth-3n:border-l-4 nth-3n:border-blue-500 p-3">
Row 5
</div>
<div class="even:bg-gray-50 nth-3:bg-blue-50 nth-3n:border-l-4 nth-3n:border-blue-500 p-3">
Row 6 (nth-3 and nth-3n apply)
</div>
</div>
Expected output: Rows with alternating backgrounds, every third row highlighted, and every third row with a blue left border.
Attribute-Based Variants
/* Data attribute variants */
@variant active (&[data-active="true"], &[aria-current="page"]);
@variant completed (&[data-completed="true"]);
@variant disabled (&[disabled], &[aria-disabled="true"]);
@variant expanded (&[aria-expanded="true"]);
@variant selected (&[aria-selected="true"]);
/* Form state attributes */
@variant required (&[required]);
@variant readonly (&[readonly]);
@variant checked (&:checked, &[aria-checked="true"]);
<nav>
<a href="#" class="active:text-brand-600 active:font-bold text-gray-600 px-3 py-2" data-active="true">
Home (active)
</a>
<a href="#" class="active:text-brand-600 active:font-bold text-gray-600 px-3 py-2">
About
</a>
</nav>
<button disabled class="disabled:opacity-50 disabled:cursor-not-allowed bg-blue-600 text-white px-4 py-2 rounded">
Disabled
</button>
Expected output: Navigation links highlight based on data-active attribute. Disabled buttons show reduced opacity and not-allowed cursor.
Parent State Variants
/* Parent-based variants */
@variant parent-hover (.group:hover &);
@variant parent-focus (.group:focus &);
@variant parent-active (.group:active &);
@variant parent-open (.group[open] &);
/* Grandparent variant */
@variant grandparent-hover (.grandgroup:hover &);
/* Container parent variant */
@variant container-hover (.card-container:hover &);
<div class="group cursor-pointer border p-4 rounded-lg hover:border-blue-300 transition-colors">
<h3 class="text-lg font-bold text-gray-900 parent-hover:text-blue-600 transition-colors">
Card title (changes on group hover)
</h3>
<p class="text-gray-600 parent-hover:text-gray-900 transition-colors">
Description text.
</p>
<span class="opacity-0 parent-hover:opacity-100 transition-opacity text-blue-600 font-medium">
Read more →
</span>
</div>
Expected output: Card title, description, and link reveal change on group hover using parent-hover:. This replaces v3's group-hover syntax.
Sibling State Variants
/* Sibling variants (replaces v3 peer) */
@variant sibling-checked (.sibling:checked ~ &);
@variant sibling-focus (.sibling:focus ~ &);
@variant sibling-hover (.sibling:hover ~ &);
@variant sibling-required (.sibling:required ~ &);
/* Multiple sibling variants */
@variant sibling-checked-hover (.sibling:checked:hover ~ &);
<div class="flex items-center gap-3">
<input type="checkbox" class="sibling w-4 h-4">
<label class="sibling-checked:text-blue-600 sibling-checked:font-medium text-gray-700">
Custom checkbox with label styling
</label>
</div>
<div class="flex items-center gap-3 mt-4">
<input type="text" class="sibling border p-2 rounded focus:ring-2">
<span class="sibling-focus:text-blue-600 text-sm text-gray-500">
Helper text changes color on input focus
</span>
</div>
Expected output: Label changes color when the sibling checkbox is checked. Helper text highlights when the sibling input is focused.
Media and Container Query Variants
/* Media query variants */
@variant dark (@media (prefers-color-scheme: dark));
@variant light (@media (prefers-color-scheme: light));
@variant reduced-motion (@media (prefers-reduced-motion: reduce));
@variant high-contrast (@media (prefers-contrast: high));
/* Container query variants */
@variant container-sm (@container (min-width: 40rem));
@variant container-lg (@container (min-width: 60rem));
/* Orientation variants */
@variant portrait (@media (orientation: portrait));
@variant landscape (@media (orientation: landscape));
<div class="@container p-4">
<div class="grid grid-cols-1 container-sm:grid-cols-2 container-lg:grid-cols-3 gap-4">
<div class="bg-blue-100 p-4 rounded">Container query responsive</div>
<div class="bg-blue-200 p-4 rounded">Based on container width</div>
<div class="bg-blue-300 p-4 rounded">Not viewport width</div>
</div>
</div>
Expected output: CSS Grid changes columns based on the container's width, not the viewport. container-sm and container-lg variants handle responsive layout.
Common Mistakes
1. Complex Selectors Affecting Performance
Overly complex custom variant selectors can slow down rendering. Keep selector specificity low with :where().
2. Not Scoping Custom Variants
Custom variants apply globally. Namespace them: @variant dodatech-active (&[data-dodatech-active]).
3. Confusing Sibling Direction
Sibling selectors (~) only target subsequent siblings, not previous ones. Plan HTML structure accordingly.
4. Missing Attribute Variants for ARIA
ARIA attributes (aria-current, aria-expanded) need explicit @variant definitions. They are not built-in.
5. Overriding @media in Variants
Media query variants override each other based on specificity. @variant dark (@media (prefers-color-scheme: dark)) and @variant light (@media (prefers-color-scheme: light)) only one applies at a time.
Practice Questions
How do you create a variant for every 4th element?
@variant nth-4 (&:nth-child(4n)).What replaces the v3 peer variant in v4? Custom sibling variants:
@variant sibling-checked (.sibling:checked ~ &).How do you create a variant for aria-current="page"?
@variant current-page (&[aria-current="page"]).What is the parent-hover variant equivalent to in v3? It replaces
group-hover:. In v4, useparent-hover:with the.groupclass.How do you create a container query variant?
@variant container-sm (@container (min-width: 40rem)).
Challenge
Create a complete custom variant system for: 3 structural variants (even, odd, nth-4), 3 attribute variants (data-active, aria-current, data-loading), 2 sibling variants (checked, focus), and 2 container query variants.
FAQ
Mini Project
Build a data table component using custom variants: even/odd row colors, nth-4 column highlighting, data-active row selection, sibling-checked for select-all checkbox, parent-hover for row actions, and container-sm for responsive column visibility.
What's Next
Now master Dark Mode in v4 for the new dark mode configuration. Then explore Container Queries for container-based Responsive Design.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro