Tailwind CSS Cheatsheet

States and Variants

Use this Tailwind CSS reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

How Variants Work

Prefix any utility with a variant followed by : to apply it conditionally:

<button class="bg-blue-600 hover:bg-blue-700 focus:ring-2 active:scale-95 disabled:opacity-50">
  Button
</button>

Variants can be stacked:

<button class="dark:hover:bg-blue-500">Stacked: dark mode + hover</button>
<a class="sm:hover:underline">Responsive + hover</a>
<input class="focus:invalid:border-red-500" placeholder="Focus + invalid">

User Interaction Variants

VariantWhen
hover:pointer hovering
focus:element has focus (keyboard or mouse)
focus-within:element or any descendant has focus
focus-visible:focus is visible (keyboard only, not mouse)
active:element is being pressed/clicked
visited:<a> has been visited
target:element is the URL fragment target
<!-- Keyboard-only focus ring (no ring on mouse click) -->
<button class="focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 outline-none">
  Accessible Button
</button>

<!-- Highlight enclosing card when any input inside is focused -->
<div class="focus-within:ring-2 focus-within:ring-blue-500 rounded-xl p-4 border">
  <input class="outline-none w-full" type="text">
</div>

Form State Variants

VariantCSS pseudo-class
disabled::disabled
enabled::enabled
checked::checked
indeterminate::indeterminate
default::default
required::required
valid::valid
invalid::invalid
in-range::in-range
out-of-range::out-of-range
placeholder-shown::placeholder-shown
autofill::autofill
read-only::read-only
optional::optional
<!-- Styled checkbox -->
<input type="checkbox" class="size-4 rounded border-gray-300 checked:bg-blue-600 checked:border-transparent focus:ring-2">

<!-- Floating label pattern -->
<div class="relative">
  <input class="peer border rounded px-3 pt-5 pb-1 placeholder-transparent w-full" placeholder="Email">
  <label class="absolute left-3 top-1 text-xs text-gray-500 transition-all peer-placeholder-shown:top-3 peer-placeholder-shown:text-base">
    Email
  </label>
</div>

<!-- Real-time validation styling -->
<input type="email" class="border rounded px-3 py-2 invalid:[&:not(:placeholder-shown)]:border-red-500 valid:[&:not(:placeholder-shown)]:border-green-500">

Structural / Position Variants

VariantCSS pseudo-class
first::first-child
last::last-child
only::only-child
odd::nth-child(odd)
even::nth-child(even)
nth-[3]::nth-child(3)
nth-last-[2]::nth-last-child(2)
first-of-type::first-of-type
last-of-type::last-of-type
only-of-type::only-of-type
nth-of-type-[2]::nth-of-type(2)
empty::empty
<!-- Remove border on last list item -->
<ul class="divide-y divide-slate-200">
  <li class="py-3 last:border-0">Item</li>
  <li class="py-3 last:border-0">Item</li>
  <li class="py-3 last:border-0">Last item — no border</li>
</ul>

<!-- Zebra striping -->
<tr class="even:bg-slate-50">...</tr>

<!-- Extra padding on first and last in list -->
<li class="px-4 first:pt-4 last:pb-4 py-2">...</li>

Parent State (group)

Style children based on parent's state.

<!-- Mark parent -->
<div class="group">
  <!-- Style child based on parent hover -->
  <h3 class="group-hover:text-blue-600 transition-colors">Title</h3>
  <p class="group-hover:opacity-100 opacity-0 transition-opacity">Revealed</p>
</div>

<!-- group-focus, group-active, group-checked also work -->
<label class="group">
  <input type="checkbox" class="sr-only peer">
  <span class="group-has-[:checked]:bg-blue-600">...</span>
</label>

Named Groups (nested groups)

<div class="group/card hover:shadow-lg">
  <div class="group/button">
    <span class="group-hover/card:text-blue-600">Changes on card hover</span>
    <span class="group-hover/button:underline">Changes on button hover</span>
  </div>
</div>

Sibling State (peer)

Style an element based on its preceding sibling's state.

<!-- Style label based on input state -->
<input class="peer border rounded px-3 py-2" type="checkbox">
<label class="peer-checked:text-blue-600 peer-checked:font-semibold">
  Checked label
</label>

<!-- Error message appears when input is invalid -->
<input class="peer border rounded px-3 py-2 invalid:border-red-500" type="email">
<p class="hidden peer-invalid:block text-red-500 text-sm mt-1">Invalid email</p>

Named Peers

<input class="peer/email" type="email" id="email">
<p class="hidden peer-invalid/email:block text-red-500">Invalid</p>

has() Variant

Style a parent based on whether it contains a matching child.

<!-- Card that highlights when it contains a checked checkbox -->
<div class="has-[:checked]:bg-blue-50 has-[:checked]:border-blue-300 border rounded-xl p-4">
  <input type="checkbox">
  <label>Option</label>
</div>

<!-- Form submission button disabled when form has invalid inputs -->
<button class="group-has-[:invalid]:opacity-50 group-has-[:invalid]:cursor-not-allowed">
  Submit
</button>

Data Attribute Variants

Apply styles based on data-* attribute values.

<!-- Generic data-* presence -->
<div data-active class="data-[active]:bg-blue-600 data-[active]:text-white px-4 py-2 rounded">
  Active tab
</div>

<!-- With specific value -->
<div data-state="open" class="data-[state=open]:block hidden">Dropdown</div>
<div data-state="closed" class="data-[state=closed]:animate-slide-out">...</div>

ARIA Variants

Style based on ARIA attribute values.

<button aria-expanded="true" class="aria-expanded:bg-slate-100">Toggle</button>
<div aria-hidden="true" class="aria-hidden:invisible">...</div>
<input aria-invalid="true" class="aria-invalid:border-red-500 aria-invalid:ring-red-500">

<!-- Custom aria value -->
<div aria-[sort=ascending]:text-blue-600>...</div>

Not Variant

Negate a selector:

<!-- Apply styles to all elements that are NOT the last child -->
<li class="not-last:border-b py-3">Item</li>

<!-- Not disabled -->
<button class="not-disabled:hover:bg-blue-700">...</button>

<!-- Not checked -->
<input type="checkbox" class="not-checked:border-gray-300">

In Variant (parent scope)

Apply styles when the element is inside a matching parent:

<!-- Style links differently when inside a nav -->
<a class="in-[nav]:font-medium in-[nav]:text-slate-700" href="#">Link</a>

<!-- Nested context styling -->
<p class="in-[.prose]:text-slate-700">...</p>

Pseudo-element Variants

VariantPseudo
before:::before
after:::after
placeholder:::placeholder
file:::file-selector-button
marker:::marker (list bullets)
selection:::selection
first-line:::first-line
first-letter:::first-letter
backdrop:::backdrop (for <dialog>, etc.)
<!-- Styled placeholder -->
<input class="placeholder:text-slate-400 placeholder:italic border rounded px-3 py-2" placeholder="Search...">

<!-- Custom list markers -->
<ul class="marker:text-blue-500 marker:font-bold list-disc pl-6">
  <li>Item</li>
</ul>

<!-- Custom text selection color -->
<p class="selection:bg-yellow-200 selection:text-yellow-900">
  Highlight this text
</p>

<!-- Decorative before/after -->
<span class="before:content-['✓'] before:text-green-500 before:mr-2">Done</span>

<!-- Custom file input button -->
<input type="file" class="file:mr-4 file:py-2 file:px-4 file:border-0 file:rounded-lg file:bg-blue-600 file:text-white file:font-medium hover:file:bg-blue-700">

Open / Closed Variants

For <details>, <dialog>, and <select> elements with open/closed states:

<details class="group border rounded-lg">
  <summary class="flex justify-between items-center p-4 cursor-pointer">
    Question
    <span class="group-open:rotate-180 transition-transform"></span>
  </summary>
  <div class="px-4 pb-4">Answer content</div>
</details>

<dialog class="backdrop:bg-black/50 open:flex flex-col">
  ...
</dialog>

Combining Multiple Variants

Variants stack left-to-right, all must match:

<!-- dark mode + hover + focus -->
<button class="dark:hover:bg-slate-700 dark:focus:ring-slate-500">...</button>

<!-- responsive + state -->
<input class="sm:focus:ring-2 lg:focus:ring-4">

<!-- group + responsive -->
<p class="sm:group-hover:underline">...</p>

Arbitrary Variant

One-off pseudo-class or selector using bracket notation:

<!-- :nth-child(3) -->
<li class="[&:nth-child(3)]:font-bold">...</li>

<!-- Complex selector -->
<div class="[&>p]:mb-4 [&>h2]:text-2xl">
  <p>Paragraph with bottom margin</p>
  <h2>Heading styled by parent</h2>
</div>

<!-- Multiple selectors -->
<ul class="[&_li]:py-2 [&_li:first-child]:pt-0">...</ul>

<!-- Combine with responsive -->
<div class="lg:[&>p]:text-lg">...</div>

Supports Variant

Apply styles only when the browser supports a CSS feature:

<div class="supports-[display:grid]:grid supports-[not(display:grid)]:flex">
  ...
</div>

<div class="supports-[backdrop-filter]:backdrop-blur-md bg-white/30">
  ...
</div>