Drive Styles From State Attributes
Data & ARIA Attribute Variants
Target data-* and aria-* attributes with variants, the natural fit for headless UI libraries that expose state as attributes.
What you'll learn
- Use aria-expanded and aria-selected variants
- Use data-[state=open] style variants
- Pair attribute variants with Radix or Headless UI
Many components express their state through aria-* and data-* attributes. Tailwind can read those attributes as variants, so styling stays declarative and the JavaScript only flips an attribute.
ARIA Variants
Common ARIA states have shorthand variants. A disclosure chevron that rotates when its trigger is expanded:
<button aria-expanded="true"
class="flex items-center gap-2">
Menu
<svg class="transition-transform aria-expanded:rotate-180">...</svg>
</button>
<li role="option" aria-selected="true"
class="px-3 py-2 aria-selected:bg-blue-600 aria-selected:text-white">
Option
</li> When the attribute is truthy the variant applies. Arbitrary forms like aria-[sort=ascending]:bg-gray-100 cover any ARIA attribute and value.
Data Variants
Headless libraries such as Radix and Headless UI toggle attributes like data-state="open". Style them directly:
<div data-state="open"
class="hidden data-[state=open]:block">
Popover panel
</div>
<button data-loading
class="data-[loading]:opacity-50 data-[loading]:pointer-events-none">
Submit
</button> data-[state=open]:block reveals the panel when the library opens it. A bare data-[loading] matches whenever the attribute is present, regardless of its value.
Pairing With Headless Libraries
This is why headless component kits and Tailwind pair so well — the library owns behaviour and accessibility, you own appearance:
<div data-orientation="vertical"
class="flex data-[orientation=vertical]:flex-col">
Tabs list
</div> You never write effect hooks to add and remove classes; you write one variant rule and let the library manage the attribute.
Form State Variants →