Tailwind CSS Cheatsheet
States and Variants
Use this Tailwind CSS reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
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>