Skip to content

Tailwind CSS v4 Custom Variants — Advanced Selector Patterns

DodaTech Updated 2026-06-28 5 min read

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 &rarr;
  </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

  1. How do you create a variant for every 4th element? @variant nth-4 (&:nth-child(4n)).

  2. What replaces the v3 peer variant in v4? Custom sibling variants: @variant sibling-checked (.sibling:checked ~ &).

  3. How do you create a variant for aria-current="page"? @variant current-page (&[aria-current="page"]).

  4. What is the parent-hover variant equivalent to in v3? It replaces group-hover:. In v4, use parent-hover: with the .group class.

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

Do custom variants work with all utilities?

Yes. Custom variants can prefix any utility class, just like built-in variants.

Can I use @variant inside media queries?

Yes. @variant definitions can include @media and @container conditions.

How do I debug a custom variant?

Inspect the compiled CSS to see if the selector is generated correctly. Use DevTools to test the selector matches.

Can custom variants be responsive?

Yes. Compose with media queries: @variant md-dark (@media (min-width: 768px) { @media (prefers-color-scheme: dark) { & } })

Are there limits on variant nesting?

Deep nesting of @variant inside @variant can be hard to read. Keep 2-3 levels maximum.

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