Structural, Data and ARIA Variants

Intermediate
12 min

Structural, Data and ARIA Variants

Hover and focus cover interaction, and group/peer cover relationships. A third family of variants targets an element by its position among siblings, by attributes that JavaScript sets, or by pseudo-elements. After this lesson you will be able to style first and last items, alternate rows, react to aria-* and data-* attributes, and add ::before/::after content without extra CSS.

Position among siblings

Structural variants map onto CSS pseudo-classes:

| Variant | Selector | Typical use | |---|---|---| | first: / last: | :first-child / :last-child | Remove the trailing border, round outer corners | | odd: / even: | :nth-child(odd/even) | Zebra-striped tables | | only: | :only-child | Different layout for a single item | | nth-3: / nth-last-2: | :nth-child(3) | Highlight a specific position | | nth-[2n+1_of_.item]: | Arbitrary nth-child formula | Skip non-matching siblings | | empty: | :empty | Hide containers with no content |

html
<table class="w-full"> <tr class="border-b last:border-b-0 even:bg-gray-50 hover:bg-blue-50"> <td class="p-2 first:ps-4 last:pe-4">Cell</td> </tr> </table>

first-of-type: and last-of-type: are the alternatives when siblings are mixed elements.

Styling children from the parent

Sometimes the children are generated by a component you do not control. The *: variant styles direct children and **: styles all descendants, so the classes live on the parent:

html
<ul class="*:rounded-full *:border *:px-3 *:py-1 flex gap-2"> <li>Sales</li> <li>Marketing</li> </ul> <div class="**:data-avatar:size-8 **:data-avatar:rounded-full"> <!-- any nested element with data-avatar becomes a small round image --> </div>

*:first:pt-0 reads left to right: "the direct child that is the first child". Because *: uses :where(), a class on the child itself still wins when you need an exception.

data-* and aria-* attributes

JavaScript libraries express state through attributes rather than classes: data-state="open", aria-expanded="true", aria-selected. Tailwind reads them directly:

html
<div data-state="open" class="data-[state=open]:block data-[state=closed]:hidden">Panel</div> <button aria-pressed="true" class="aria-pressed:bg-blue-600 aria-pressed:text-white">Bold</button> <th aria-sort="ascending" class="aria-[sort=ascending]:bg-gray-100">Name</th> <li data-active class="data-active:font-bold">Boolean attribute</li>

The common boolean ARIA states are built in — aria-checked:, aria-disabled:, aria-expanded:, aria-hidden:, aria-pressed:, aria-readonly:, aria-required:, aria-selected:, aria-busy: — and match ="true". Any other attribute or value uses the arbitrary form aria-[sort=ascending]: or data-[size=large]:. All of them combine with group- and peer- prefixes, so group-aria-expanded:rotate-180 rotates an icon when its parent button is expanded. Because the attribute a screen reader announces is the same one driving the visuals, the two can never drift apart.

Pseudo-elements and the not-* variant

before: and after: generate ::before and ::after. Tailwind adds an empty content automatically, so you only set it when it should contain text:

html
<label class="after:ms-0.5 after:text-red-500 after:content-['*']">Name</label> <a class="relative after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-0 after:bg-current after:transition-all hover:after:w-full"> Animated underline </a>

Other pseudo-element variants include placeholder:, marker: (list bullets), selection: (highlighted text), file: (file input button), first-line:, first-letter: and backdrop: (dialog backdrops).

not-* negates any variant: not-first:mt-4 adds top margin to every item except the first, not-hover:opacity-75 dims everything that is not hovered, and not-[.active]:text-gray-500 uses an arbitrary selector.

Common mistakes

  • Expecting first: to work on a wrapper: it applies to the element carrying the class, which must be the first child of its parent.
  • Writing data-[state="open"] with quotes; write data-[state=open].
  • Using after: for essential text; generated content is unreliable for screen readers.
Quick Quiz
Question 1 of 2

Which variant removes the bottom border from the last item of a list?

Key Takeaways

  • first:, last:, odd:, even:, only: and nth-* style elements by their position among siblings.
  • *: and **: push styles down to children and descendants from the parent.
  • Built-in aria-* and data-* variants, plus arbitrary data-[key=value]:, react to attributes set by JavaScript.
  • before: and after: create pseudo-elements with automatic empty content; add content-['…'] for text.
  • not-* negates any variant for "everything except" rules.

Next lesson: Dark Mode — build a color scheme that responds to system preference or a manual toggle with the dark: variant.

Structural, Data and ARIA Variants - Tailwind CSS | CodeYourCraft | CodeYourCraft