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
| Variant | When |
|---|---|
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
| Variant | CSS 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
| Variant | CSS 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
| Variant | Pseudo |
|---|---|
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>