Cheatsheet Tailwind CSS
Framework CSS utility-first
Tailwind CSS
Installation and Configuration
Installation (Vite + v4)
npm install -D tailwindcss @tailwindcss/vite
// vite.config.js
import tailwindcss from '@tailwindcss/vite'
export default {
plugins: [tailwindcss()]
}
/* input.css */
@import "tailwindcss";
/* Build: npm run dev / npm run build */In Tailwind v4, install @tailwindcss/vite and add it to the plugins array. The CSS uses only @import "tailwindcss" (it replaces the 3 @tailwind base/components/utilities directives from v3). Vite processes everything automatically without needing postcss.config.js or tailwind.config.js.
Spacing scale
/* 1 unit = 0.25rem (4px) */ p-1 = 0.25rem (4px) p-2 = 0.5rem (8px) p-4 = 1rem (16px) p-8 = 2rem (32px) p-16 = 4rem (64px) /* Available values: */ 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80, 96 /* Same scale for: p, m, w, h, gap, size, space */
The scale is consistent across the whole framework: each unit = 0.25rem (4px). p-4 = 16px, m-8 = 32px, w-16 = 64px. The same scale applies to padding, margin, width, height, gap, size and space. Larger values (72, 80, 96) are for layouts and sections. Use [value] for arbitrary values outside the scale.
Official plugins
// tailwind.config.js (v3)
module.exports = {
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
require('@tailwindcss/container-queries'),
]
}
/* v4: import in as CSS */
@plugin "@tailwindcss/forms";
@plugin "@tailwindcss/typography";Official plugins extend Tailwind: @tailwindcss/forms styles inputs with a consistent reset. typography adds the prose class for rich HTML content (markdown, CMS). aspect-ratio for proportions (videos). container-queries for styles based on the container. In v4, use @plugin in the CSS instead of require().
Laravel + Blade + Vite
/* 1. Install */
npm install -D tailwindcss @tailwindcss/vite
/* 2. resources/css/app.css */
@import "tailwindcss";
/* 3. vite.config.js */
import tailwindcss from '@tailwindcss/vite'
export default {
plugins: [laravel({ input: ['resources/css/app.css',
'resources/js/app.js'], refresh: true }),
tailwindcss()]
}
/* 4. Blade: */
@vite(['resources/css/app.css', 'resources/js/app.js'])Integration with Laravel: install Tailwind + the Vite plugin, import it in app.css, configure vite.config.js with the laravel() and tailwindcss() plugins. In Blade, use the @vite directive to load the assets. With refresh: true, changes to Blade files reload the browser automatically.
Installation (PostCSS / v3)
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.js
module.exports = {
content: ["./src/**/*.{html,js,vue,blade.php}"],
theme: { extend: {} },
plugins: [],
}
/* input.css */
@tailwind base;
@tailwind components;
@tailwind utilities;In v3, use postcss + autoprefixer. The content field defines the files that Tailwind scans to generate only the CSS used (tree-shaking). The 3 @tailwind directives import os layers: base (reset/preflight), components and utilities. The init -p also creates the postcss.config.js.
Customize theme (v3 config)
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
brand: '#1fb6ff',
'brand-dark': '#009eeb',
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
display: ['Clash Display', 'sans-serif'],
},
spacing: { '128': '32rem' },
borderRadius: { '4xl': '2rem' },
}
}
}theme.extend adds values without replacing Tailwind's defaults. colors creates custom colors usable the bg-brand, text-brand. fontFamily defines fonts (font-sans, font-display). spacing adds values to the scale (p-128). Without extend, it replaces EVERYTHING — always use extend so you don't lose the defaults.
Prettier (class order)
npm install -D prettier prettier-plugin-tailwindcss
// .prettierrc
{
"plugins": ["prettier-plugin-tailwindcss"]
}
/* Before (unordered): */
class="text-white p-4 bg-blue-500 rounded flex mt-2"
/* After (automatically ordered): */
class="mt-2 flex rounded bg-blue-500 p-4 text-white"The prettier-plugin-tailwindcss plugin orders the classes automatically in Tailwind's canonical order (layout → flex → grid → spacing → sizing → typography → colors → effects). It eliminates inconsistencies across the team and makes reading easier. It works with HTML, Vue, React, Blade, Astro. Highly recommended in any project.
content (tree-shaking)
// tailwind.config.js (v3)
module.exports = {
content: [
"./index.html",
"./src/**/*.{html,js,ts,vue,jsx,tsx}",
"./resources/views/**/*.blade.php",
"./app/Livewire/**/*.php",
],
}
/* v4: automatic detection (no config) */
/* Use @source to add extra paths: */
@source "../vendor/laravel/framework/src";content tells Tailwind which files to scan to extract used classes. Only the CSS of the classes found is generated (tree-shaking). Include ALL files with classes: HTML, JS, Vue, Blade, PHP (Livewire). In v4, detection is automatic in most cases. Use @source for paths outside the project (e.g.: vendor). If a class doesn't show up, the file is probably missing from content.
Via CDN (Play CDN)
<!-- For prototypes and development only -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Inline configuration (optional): -->
<script>
tailwind.config = {
theme: {
extend: {
colors: { brand: '#1fb6ff' }
}
}
}
</script>
<!-- Immediate use: -->
<div class="p-4 bg-brand text-white">Hello!</div>The Play CDN is only for prototypes and learning — do not use it in production (it generates CSS at runtime, without tree-shaking). It lets you test classes immediately without a build. Configuration is done via the inline tailwind.config object. For production, always use Vite or PostCSS.
Customize theme (v4 CSS)
/* input.css — Tailwind v4 */
@import "tailwindcss";
@theme {
--color-brand: #1fb6ff;
--color-brand-dark: #009eeb;
--font-sans: 'Inter', sans-serif;
--font-display: 'Clash Display', sans-serif;
--spacing-128: 32rem;
--breakpoint-3xl: 1920px;
--radius-4xl: 2rem;
}
/* Usage: bg-brand, font-display, p-128, 3xl:flex, rounded-4xl */In v4, configuration is done in CSS with @theme — you don't need tailwind.config.js. Define --color-*, --font-*, --spacing-*, --breakpoint-* variables. Each variable automatically generates utilities: --color-brand → bg-brand, text-brand, border-brand. Simpler and with autocomplete in the editor.
CLI and build
/* Build for production (v3 standalone CLI) */ npx tailwindcss -i input.css -o output.css --minify /* Watch in development */ npx tailwindcss -i input.css -o output.css --watch /* v4 with Vite (no separate CLI needed) */ npm run dev /* vite dev — hot reload */ npm run build /* vite build — optimized CSS */ /* The final output is tiny (tree-shaking) */ /* Large project: ~10-30kb of final CSS */
The CLI compiles the CSS: -i (input), -o (output), --minify (production), --watch (development with hot reload). In v4 with Vite, the build is integrated (vite build). The final output is tiny because tree-shaking removes all classes not used in the HTML. A large project typically generates 10-30kb of CSS.
Basic usage (utility-first)
<div class="p-4 bg-blue-500 text-white rounded-lg shadow-md"> Hello Tailwind! </div> <button class="px-6 py-2 bg-green-600 text-white font-semibold rounded-full hover:bg-green-700 transition duration-200"> Click here </button> <a href="#" class="text-blue-600 underline hover:text-blue-800">Link</a>
The utility-first concept: compose the design with small classes directly in the HTML, without writing custom CSS. p-4 = 1rem padding, bg-blue-500 = blue background, rounded-lg = rounded corners, shadow-md = medium shadow. Each class does ONE thing. The result is a consistent design with no separate CSS files.
Arbitrary values
/* Any value with [ ] */
w-[300px]
h-[calc(100vh-64px)]
bg-[#1fb6ff]
text-[13px]
grid-cols-[1fr_2fr_1fr]
top-[117px]
shadow-[0_0_10px_rgba(0,0,0,0.3)]
tracking-[0.2em]
max-w-[1400px]
z-[999]
/* Underscores = spaces */
bg-[url('/img/hero.png')]Brackets [ ] allow any CSS value without configuring the theme. w-[300px] = exact width of 300px. bg-[#1fb6ff] = custom hex color. grid-cols-[1fr_2fr] = custom template. Use underscores instead of spaces inside the brackets. Ideal for unique values that don't warrant a theme entry.
Preflight (CSS reset)
/* Tailwind applies a global reset (preflight): */
- margin: 0 on all elements
- box-sizing: border-box globally
- border: 0 solid (no border by default)
- img, svg, video: display block, max-width 100%
- button, input: font inherit, no styles
- h1-h6, p: font-size/weight inherited
- a: color inherit, text-decoration inherit
- ul, ol: list-style none, padding 0
/* Disable (not recommended): */
corePlugins: { preflight: false }preflight is Tailwind's CSS reset (based on modern-normalize). It removes the browser's default margins, paddings and styles. It sets box-sizing: border-box globally. Images become display: block with max-width: 100%. Buttons and inputs inherit the font. That's why you need to add border explicitly to see borders.
Layout e Display
Display
block /* display: block */ inline-block /* display: inline-block */ inline /* display: inline */ flex /* display: flex */ inline-flex /* display: inline-flex */ grid /* display: grid */ inline-grid /* display: inline-grid */ hidden /* display: none */ contents /* display: contents */ table /* display: table */ /* Responsive: */ class="hidden md:flex" /* hidden on mobile, flex on md+ */ class="block lg:hidden" /* visible only on mobile */
block, inline, flex, grid control the element's display type. hidden completely removes it from the flow (like display: none). contents makes the element "disappear" while keeping the children visible in the layout. Combine with breakpoints to show/hide responsively: hidden md:flex is the most used pattern.
Z-index
z-0, z-10, z-20, z-30, z-40, z-50 z-auto /* z-index: auto */ -z-10 /* negative (behind) */ z-[100] /* arbitrary */ /* Suggested convention: */ /* Base content: z-0 (or no z-index) */ /* Dropdown/menu: z-10 */ /* Sticky header: z-20 */ /* Sidebar: z-30 */ /* Modal overlay: z-40 */ /* Modal/toast: z-50 */ /* Requires position: relative/absolute/fixed/sticky */
z-0 to z-50 control the stacking order (which element goes on top). z-auto = no z-index. Use z-[100] for custom values. Adopt a team convention: content z-0, dropdowns z-10, sticky z-20, modals z-50. It only works with position (relative, absolute, fixed or sticky).
Float and clear
float-start /* left in LTR, right in RTL */ float-end /* right in LTR, left in RTL */ float-none /* no float */ clear-both /* clears floats */ clearfix /* ::after pseudo-element on the parent */ /* Legacy use (text wrapping around an image): */ <img class="float-start me-4 mb-2 w-32" src="photo.jpg"> <p>Text flui à volta da image...</p> /* TODAY: prefer flex or grid for layouts */
float-* is legacy — today prefer flex or grid for layouts. float-start/float-end are RTL-aware (they replace float-left/float-right). clearfix clears floats on the parent (avoids height collapse). Kept for compatibility with old layouts and for the use case of text flowing around an image.
Box sizing
box-border /* width includes padding + border (TW default) */ box-content /* width of the content only */ /* Tailwind applies box-border globally via preflight */ /* Example with box-content: */ <div class="box-content w-32 p-4 border-2"> /* Content: 128px */ /* Total rendered: 128 + 32 (padding) + 4 (border) = 164px */ </div> /* With box-border (default): */ <div class="box-border w-32 p-4 border-2"> /* Total: 128px (content shrinks to fit) */ </div>
Tailwind applies box-sizing: border-box globally via preflight. box-border = the width includes padding and border (more intuitive). box-content = width is content only (original CSS behavior). You rarely need to change it — border-box is more predictable because w-32 is always 128px regardless of padding.
Container
<div class="container mx-auto px-4">
Content centered com padding lateral
</div>
/* Config (v3): */
container: {
center: true,
padding: { DEFAULT: '1rem', sm: '2rem' },
screens: { '2xl': '1400px' }
}
/* v4 (CSS): */
@utility container {
margin-inline: auto;
padding-inline: 1rem;
}container defines a responsive max width that changes at each breakpoint (640px, 768px, 1024px, 1280px, 1536px). mx-auto centers it horizontally. px-4 adds side padding so it doesn't stick to the edges. In v3, set center: true to center automatically. In v4, use @utility to customize it.
Overflow
overflow-auto /* scroll if needed */ overflow-hidden /* cuts the excess */ overflow-visible /* shows (default) */ overflow-scroll /* always scroll */ overflow-x-auto /* horizontal scroll */ overflow-y-hidden /* cuts vertical */ overflow-clip /* cuts without creating a scroll container */ /* Truncate text (1 line): */ class="truncate" /* = overflow-hidden + text-ellipsis + whitespace-nowrap */ /* Wide table on mobile: */ <div class="overflow-x-auto"> <table class="w-full">...</table> </div>
overflow-hidden cuts excess content — essential with rounded-* so images don't overflow the corners. overflow-x-auto for wide tables on mobile (horizontal scroll). truncate = shortcut for single-line text with ellipsis. overflow-clip cuts without creating a scroll container (more performant). overflow-auto shows scroll only when needed.
Visibility and object-fit
visible /* visibility: visible */ invisible /* visibility: hidden (takes up space) */ collapse /* visibility: collapse (tables) */ /* Object-fit (images/videos): */ object-contain /* fits everything (may show bars) */ object-cover /* fills by cropping */ object-fill /* stretches (distorts) */ object-none /* original size */ object-scale-down /* smaller of none and contain */ /* Uniform thumbnail in a grid: */ <img class="w-full h-48 object-cover" src="photo.jpg">
invisible hides but keeps the space occupied (unlike hidden which removes it from the flow). object-cover fills the container by cropping the excess (thumbnails, hero images). object-contain shows the whole image (may show bars). Combine with w-full h-48 for uniformly sized images in grids. object-fill distorts — avoid it.
Classic layout (sidebar + main)
/* Flexbox: */ <div class="flex min-h-screen"> <aside class="w-64 shrink-0 bg-gray-100 p-4">Sidebar</aside> <main class="flex-1 p-8">Content</main> </div> /* Grid: */ <div class="grid grid-cols-1 md:grid-cols-[250px_1fr] min-h-screen"> <aside class="bg-gray-100 p-4">Sidebar</aside> <main class="p-8">Content</main> </div> /* Responsive: sidebar disappears on mobile */ <aside class="hidden md:block w-64">Sidebar</aside>
Two patterns for sidebar + content: flex with w-64 shrink-0 (fixed sidebar) + flex-1 (main stretches), or grid with grid-cols-[250px_1fr] (explicit template). min-h-screen guarantees a full-screen minimum height. Responsive: hidden md:block hides the sidebar on mobile. Grid is more explicit; flex is more flexible.
Position
static /* default (no positioning) */ relative /* relative to its normal position */ absolute /* relative to the nearest relative parent */ fixed /* relative to the viewport (always visible) */ sticky /* sticks on scroll when it reaches the offset */ /* Classic pattern: relative parent + absolute child */ <div class="relative"> <div class="absolute top-0 right-0">Badge</div> </div> /* Navbar fixed on scroll: */ <nav class="sticky top-0 z-50">Menu</nav>
relative on the parent + absolute on the child is the positioning pattern. sticky top-0 sticks to the top when scrolling (navbar, table headers). fixed = always visible in the viewport (toasts, FAB, modals). static is the default (no positioning). Coordinates: top-0, right-0, inset-0 (all sides). Always combine with z-index to control the layers.
Aspect ratio
aspect-auto /* natural proportion */ aspect-square /* 1/1 (square) */ aspect-video /* 16/9 (video) */ aspect-[4/3] /* arbitrary */ aspect-[21/9] /* ultrawide */ /* Responsive video: */ <div class="aspect-video w-full"> <iframe class="w-full h-full" src="..."></iframe> </div> /* Square avatar: */ <img class="aspect-square w-24 object-cover rounded-full">
aspect-video = 16:9 proportion for responsive videos (YouTube, Vimeo). aspect-square = 1:1 for avatars and thumbnails. aspect-[4/3] for custom ratios. It replaces the old percentage padding-bottom hack. The content stretches automatically to fill the defined proportion. Combine with object-cover for images.
Pointer events and user select
pointer-events-none /* not clickable (overlay, loading) */ pointer-events-auto /* restores clicks */ select-none /* text not selectable */ select-text /* selectable (default) */ select-all /* selects everything on click */ select-auto /* Loading overlay (doesn't block clicks): */ <div class="absolute inset-0 bg-white/50 pointer-events-none flex items-center justify-center"> <span class="animate-spin text-2xl">⟳</span> </div> /* Button with icon (icon doesn't interfere): */ <button><svg class="pointer-events-none">...</svg> Text</button>
pointer-events-none disables clicks on the element (decorative overlays, icons inside buttons, loading spinners). select-none prevents text selection (buttons, labels, UI). select-all selects everything on click (codes, URLs, API keys). Combine with pointer-events-auto on a child to re-enable clicks on specific elements inside a disabled parent.
Coordinates (inset)
top-0, top-1/2, top-full, -top-4 bottom-0, left-0, right-0 inset-0 /* top+right+bottom+left: 0 */ inset-x-0 /* left + right: 0 */ inset-y-0 /* top + bottom: 0 */ start-0 /* left in LTR, right in RTL */ end-0 /* right in LTR, left in RTL */ /* Center with absolute (pattern): */ class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" /* Full-screen overlay: */ class="absolute inset-0 bg-black/50"
inset-0 = all sides at 0 (full-screen overlay, backgrounds). inset-x-0 = horizontal only. start/end are logical properties (RTL-aware). To center an element: absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2. Negative values with the - prefix (e.g.: -top-4 to overlap).
Columns (multi-column)
columns-1, columns-2, columns-3, columns-4 columns-auto columns-[250px] /* minimum width per column */ <div class="columns-2 md:columns-3 gap-8"> <p class="break-inside-avoid mb-4">Text...</p> <p class="break-inside-avoid mb-4">Text...</p> <img class="break-inside-avoid mb-4" src="photo.jpg"> </div> /* break-inside-avoid prevents a break in the middle of the element */
columns-* creates a multi-column layout (like a newspaper/magazine). columns-2 md:columns-3 = 2 columns on mobile, 3 on md+. gap-8 spaces the columns. break-inside-avoid prevents an element from being split between columns. Ideal for link lists, FAQs, masonry galleries and editorial content. The content flows top to bottom in each column.
Isolation and contain
isolate /* isolation: isolate (new stacking context) */ isolation-auto contain-none contain-layout /* isolates layout */ contain-paint /* isolates paint (clip) */ contain-size /* isolates size */ contain-strict /* layout + paint + size */ contain-content /* layout + paint */ /* Problem: a child with -z-10 ends up behind the parent */ /* Solution: isolate on the parent */ <div class="isolate relative bg-white"> <div class="absolute -z-10">Background decorativo</div> </div>
isolate creates a new stacking context — it prevents children with a negative z-index from ending up behind the parent (a common problem with decorative backgrounds). contain-* optimizes performance by isolating layout/paint from the rest of the page. contain-paint prevents children from visually "leaking" out. Useful for complex components, animations and long lists.
Spacing and Dimensions
Padding
p-4 /* all sides (1rem = 16px) */ px-4 /* left + right (horizontal) */ py-2 /* top + bottom (vertical) */ pt-4 /* top only */ pr-2 /* right only */ pb-4 /* bottom only */ pl-2 /* left only */ ps-4 /* start (LTR: left, RTL: right) */ pe-4 /* end (LTR: right, RTL: left) */ /* Responsive: */ class="p-4 md:p-8" /* more padding on desktop */
p = padding. Suffixes: x (horizontal), y (vertical), t/b/l/r (physical sides), s/e (logical sides, RTL-aware). Scale: 0 to 96 (each unit = 0.25rem). p-4 = 16px. It accepts responsive prefixes: p-4 md:p-8 = more padding on desktop.
Size (v3.4+)
size-4 /* width + height: 1rem (16px) */ size-8 /* 2rem x 2rem (32px) */ size-12 /* 3rem x 3rem (48px) */ size-full /* 100% x 100% */ size-screen /* 100vw x 100vh */ size-fit /* fit-content both */ size-auto /* Icons: */ <svg class="size-5 text-gray-500">...</svg> /* Avatar: */ <img class="size-12 rounded-full object-cover" src="avatar.jpg"> /* Square thumbnail: */ <img class="size-24 rounded-lg object-cover" src="photo.jpg">
size-* sets width AND height simultaneously (shortcut for w-* h-*). size-12 = 48px × 48px. Perfect for icons (size-5 = 20px), avatars (size-12 rounded-full) and square thumbnails (size-24). It eliminates the w-* h-* repetition. Available since Tailwind v3.4. It accepts responsive: size-12 md:size-16.
Responsive padding
/* Sections with progressive padding: */ class="py-8 md:py-16 lg:py-24" /* Container with adaptive side padding: */ class="px-4 sm:px-6 lg:px-8" /* Card with responsive padding: */ class="p-4 md:p-6 lg:p-8" /* Hero with more space: */ <section class="py-16 md:py-24 lg:py-32"> <div class="px-4 md:px-0">Content</div> </section> /* Rule: more space on larger screens */
Responsive padding is essential for professional designs: py-8 md:py-16 lg:py-24 = sections with more vertical space on larger screens. px-4 sm:px-6 lg:px-8 = progressive side padding. General rule: mobile has less space (small screen), desktop has more (more available space). It avoids "cramped" designs on desktop or "wide" ones on mobile.
Margin
m-4 /* all sides */ mx-auto /* centers horizontally */ my-4 /* top + bottom */ mt-4 /* top */ mb-0 /* removes the bottom margin */ ms-2 /* start (RTL-aware) */ -me-2 /* negative end */ -mt-4 /* negative margin-top (pulls up) */ /* Center a fixed-width block: */ <div class="max-w-4xl mx-auto px-4">Content</div> /* Overlap (card rises over the previous section): */ <div class="-mt-8 relative z-10">Card</div>
m = margin, same logic the padding. mx-auto centers fixed-width blocks (essential with max-w-*). Negative values with the - prefix: -mt-4 (pulls up), -me-2. Useful for overlaps and fine adjustments. mb-0 removes the bottom margin. ms/me are RTL-aware.
max-width (content)
max-w-xs /* 20rem (320px) */ max-w-sm /* 24rem (384px) */ max-w-md /* 28rem (448px) */ max-w-lg /* 32rem (512px) */ max-w-xl /* 36rem (576px) */ max-w-2xl /* 42rem (672px) */ max-w-4xl /* 56rem (896px) */ max-w-7xl /* 80rem (1280px) */ max-w-prose /* 65ch (ideal reading width) */ max-w-screen-xl /* 1280px (xl breakpoint) */ /* Readable article: */ <article class="max-w-prose mx-auto">Text long...</article> /* Page container: */ <div class="max-w-7xl mx-auto px-4">...</div>
max-w-* limits the maximum width. max-w-prose = 65 characters (ideal reading width for long text). max-w-7xl = 80rem (page container). max-w-screen-xl = the xl breakpoint width. Combine with mx-auto to center. Essential for readability — text at 100% width is hard to read.
min-w-0 and overflow in flex
/* PROBLEM: text doesn't truncate in flex */
<div class="flex">
<img class="size-12" src="avatar.jpg">
<p class="truncate">Text very long...</p>
<!-- truncate does NOT work without min-w-0! -->
</div>
/* SOLUTION: min-w-0 on the flexible child */
<div class="flex gap-4">
<img class="shrink-0 size-12" src="avatar.jpg">
<div class="min-w-0">
<p class="truncate">Text very long that now truncates correctly...</p>
</div>
</div>min-w-0 is ESSENTIAL in flex/grid to let the content shrink below its natural size. Without it, truncate and overflow-hidden don't work because the flex element has min-width: auto by default (it doesn't shrink below the content). Add min-w-0 to the child that needs to truncate + shrink-0 on the fixed elements.
Width
w-full /* 100% */ w-screen /* 100vw */ w-auto /* automatic */ w-1/2 /* 50% */ w-1/3 /* 33.33% */ w-2/3 /* 66.67% */ w-1/4 /* 25% */ w-3/4 /* 75% */ w-64 /* 16rem (256px) */ w-fit /* fit-content */ w-min /* min-content */ w-max /* max-content */ /* Responsive: */ class="w-full md:w-1/2 lg:w-1/3"
w-full = 100% of the parent. Fractions: w-1/2 (50%), w-2/3 (66%), w-3/4 (75%). w-fit = fits the content. w-screen = 100% of the viewport. Responsive: w-full md:w-1/2 lg:w-1/3 = full width on mobile, half on tablet, a third on desktop. Combine with max-w-* to limit it.
space-between (space between children)
/* Adds margin between children (except the 1st) */ space-x-4 /* margin-left: 1rem on children */ space-y-2 /* margin-top: 0.5rem on children */ space-x-reverse space-y-reverse /* Vertical stack (forms, lists): */ <div class="space-y-4"> <p>Paragraph 1</p> <p>Paragraph 2</p> <p>Paragraph 3</p> </div> /* TODAY: prefer flex/grid + gap */ <div class="flex flex-col gap-4"> ← modern
space-y-* adds vertical margin between children (without touching the first). Useful for lists of paragraphs, button stacks, forms. Today, flex flex-col gap-4 is preferable (simpler, no > * + * selectors). space-x-reverse inverts the direction (for RTL or flex-row-reverse). Kept for compatibility.
Responsive dimensions
/* Adaptive width: */ class="w-full md:w-1/2 lg:w-1/3" /* Adaptive height: */ class="h-48 md:h-64 lg:h-96" /* Adaptive size: */ class="size-12 md:size-16" /* Max-width per breakpoint: */ class="max-w-sm md:max-w-md lg:max-w-2xl" /* Responsive image: */ <img class="w-full h-auto md:w-1/2 md:h-64 object-cover"> /* Rule: define mobile-first and override */
Responsive dimensions follow the mobile-first pattern: define the base value (mobile) and override at larger breakpoints. w-full md:w-1/2 = 100% on mobile, 50% on md+. h-48 md:h-64 = more height on desktop. size-12 md:size-16 = larger icons/avatars. Always combine with object-cover so images don't distort.
Height
h-full /* 100% of the parent */ h-screen /* 100vh (viewport) */ h-auto /* automatic */ h-16 /* 4rem (64px) */ h-1/2 /* 50% of the parent */ max-h-96 /* max-height: 24rem */ min-h-screen /* min-height: 100vh */ min-h-0 /* min-height: 0 */ /* Full-height hero section: */ <section class="min-h-screen flex items-center">...</section> /* Modal with scroll: */ <div class="max-h-[80vh] overflow-y-auto">...</div>
h-screen = 100% of the viewport. min-h-screen = full-screen minimum height (hero sections, login pages — the content can grow). max-h-* limits the maximum height (modals, dropdowns with scroll). h-full requires the parent to have a defined height. min-h-0 is needed in flex/grid to allow overflow.
Negative margin
/* - prefix for negative values */ -mt-4 /* margin-top: -1rem (pulls up) */ -ml-2 /* margin-left: -0.5rem */ -mx-4 /* negative horizontal margin */ -inset-2 /* top/right/bottom/left: -0.5rem */ /* Card that overlaps the previous section: */ <section class="bg-blue-600 pb-16">Hero</section> <div class="-mt-8 mx-auto max-w-4xl bg-white rounded-xl shadow-lg p-8"> Overlapping card </div> /* Image that "breaks out" of the container: */ <img class="-mx-4 w-[calc(100%+2rem)]" src="wide.jpg">
Negative values with the - prefix: -mt-4 pulls the element up (overlap). Classic pattern: hero with pb-16 + card with -mt-8 (card rises over the hero). -mx-4 for images that "break out" of the container's padding. -inset-2 for slightly larger overlays. Essential for designs with overlaps and depth.
Box sizing and contain
box-border /* width includes padding + border (TW default) */ box-content /* width of the content only */ /* contain (performance): */ contain-none contain-layout /* isolates layout */ contain-paint /* isolates paint (visual clip) */ contain-size /* isolates size */ contain-strict /* layout + paint + size */ contain-content /* layout + paint */ /* Optimize long lists: */ <div class="contain-content"> <!-- 1000 items --> </div>
Tailwind applies box-sizing: border-box globally via preflight — the width includes padding and border. box-content reverts to the original behavior (width of the content only). contain-* optimizes performance: contain-paint prevents children from visually "leaking", contain-layout isolates the layout. Useful for long lists and independent components.
Typography
Font size
text-xs /* 0.75rem (12px) + line-height 1rem */ text-sm /* 0.875rem (14px) + line-height 1.25rem */ text-base /* 1rem (16px) — browser default */ text-lg /* 1.125rem (18px) */ text-xl /* 1.25rem (20px) */ text-2xl /* 1.5rem (24px) */ text-3xl /* 1.875rem (30px) */ text-4xl /* 2.25rem (36px) */ text-5xl /* 3rem (48px) */ text-6xl ... text-9xl /* Responsive: */ class="text-xl md:text-3xl lg:text-5xl"
text-* sets the font size AND the proportional line-height automatically. text-base = 16px (browser default). text-sm/text-xs for secondary text. text-4xl to text-9xl for hero titles. Responsive: text-xl md:text-3xl lg:text-5xl = smaller on mobile, larger on desktop.
Line height and letter spacing
/* Line height: */ leading-none /* 1 (no spacing) */ leading-tight /* 1.25 (titles) */ leading-snug /* 1.375 */ leading-normal /* 1.5 (default) */ leading-relaxed /* 1.625 (body text) */ leading-loose /* 2 (lots of spacing) */ /* Letter spacing (tracking): */ tracking-tighter /* -0.05em */ tracking-tight /* -0.025em (large titles) */ tracking-normal /* 0 */ tracking-wide /* 0.025em (uppercase) */ tracking-wider /* 0.05em */ tracking-widest /* 0.1em */
leading-* controls the line height (space between lines). leading-tight for titles (close lines), leading-relaxed for body text (more space, more readable). tracking-* controls the letter spacing. tracking-wide for uppercase text, tracking-tight for large titles (letters closer together).
Text overflow and wrapping
text-ellipsis /* ellipsis (with truncate) */ text-clip /* cuts without ellipsis */ whitespace-normal /* normal wrapping (default) */ whitespace-nowrap /* never wraps */ whitespace-pre /* preserves spaces and \n */ whitespace-pre-wrap /* preserves + allows wrapping */ whitespace-break-spaces /* like pre-wrap + breaks spaces */ /* Code with horizontal scroll: */ <pre class="whitespace-pre overflow-x-auto p-4 bg-gray-900 text-green-400 rounded-lg">code here</pre> /* Badge that doesn't wrap: */ <span class="whitespace-nowrap px-2 py-1 bg-gray-100 rounded">Badge</span>
text-ellipsis shows an ellipsis when cutting off (requires overflow-hidden + whitespace-nowrap). whitespace-pre-wrap preserves coin a waytting but allows line breaks. whitespace-nowrap for badges and labels that should not wrap. overflow-x-auto + whitespace-pre for code blocks with horizontal scroll.
Responsive typography
/* Scalable titles: */ <h1 class="text-3xl md:text-4xl lg:text-6xl font-bold"> Title Hero </h1> <h2 class="text-2xl md:text-3xl font-semibold"> Subtitle </h2> /* Body text: */ <p class="text-base md:text-lg leading-relaxed text-gray-600"> Paragraph com boa readability... </p> /* Rule: mobile 1 size smaller than desktop */ /* text-3xl → md:text-4xl → lg:text-5xl */
Responsive typography: titles scale with breakpoints — text-3xl md:text-4xl lg:text-6xl (smaller on mobile, larger on desktop). Body text: text-base md:text-lg with leading-relaxed for readability. Rule of thumb: mobile 1-2 sizes smaller than desktop. Avoid text smaller than text-sm (14px) for body copy. text-gray-600 for secondary text.
Font weight
font-thin /* 100 */ font-extralight /* 200 */ font-light /* 300 */ font-normal /* 400 (default) */ font-medium /* 500 */ font-semibold /* 600 */ font-bold /* 700 */ font-extrabold /* 800 */ font-black /* 900 */ /* Typical usage: */ <h1 class="text-4xl font-bold">Main title</h1> <h2 class="text-2xl font-semibold">Subtitle</h2> <p class="font-normal text-gray-600">Corpo de text</p> <span class="text-sm font-medium">Label</span>
font-* sets the font weight. font-bold (700) for titles, font-semibold (600) for subtitles and modern UI, font-medium (500) for labels and buttons, font-normal (400) for body text. Make sure the font has the required weights — load them from Google Fonts with the specific weights to avoid FOUT.
Truncate and clamp
/* One line with ellipsis: */ <p class="truncate">Text very long that cuts with ellipsis...</p> /* Multiple lines (2-6): */ <p class="line-clamp-3">Text that cuts after 3 lines e shows ellipsis no end da third line...</p> /* Word breaking: */ break-words /* breaks long words (URLs) */ break-all /* breaks at any character */ whitespace-nowrap /* never breaks (badges, labels) */ whitespace-pre-line /* preserves \n from text */ /* line-clamp requires display: -webkit-box (automatic) */
truncate = one line with ellipsis (shortcut for overflow-hidden + text-ellipsis + whitespace-nowrap). line-clamp-3 = cuts off after N lines with ellipsis (requires a box display — automatic in Tailwind). break-words avoids overflow of long URLs. whitespace-nowrap prevents breaking (badges, labels, dates).
Text transform and indent
/* Transform: */ uppercase /* UPPERCASE */ lowercase /* lowercase */ capitalize /* First Letter Of Each Word */ normal-case /* normal (removes transform) */ /* Indentation: */ indent-4 /* text-indent: 1rem */ indent-8 /* text-indent: 2rem */ -indent-4 /* negative */ /* Hyphens: */ hyphens-none | hyphens-manual | hyphens-auto /* Classic label: */ <p class="uppercase tracking-wide text-sm font-semibold text-gray-500"> Category </p>
uppercase + tracking-wide + text-sm + font-semibold = classic pattern for labels, badges and categories. capitalize for titles. indent-* adds an indent on the first line (editorial text). hyphens-auto for justified text in narrow columns (requires lang in the HTML).
Prose (typography plugin)
/* Plugin: @tailwindcss/typography */ <article class="prose prose-lg"> <h1>Title do artigo</h1> <p>Paragraph com formatting automatic...</p> <ul><li>Item 1</li><li>Item 2</li></ul> <blockquote>Quote</blockquote> <pre><code>code</code></pre> </article> /* Sizes: prose-sm, prose, prose-lg, prose-xl, prose-2xl */ /* Colors: prose-slate, prose-gray, prose-zinc */ /* Dark: dark:prose-invert */ <article class="prose dark:prose-invert max-w-prose">
The @tailwindcss/typography plugin adds the prose class that automatically styles rich HTML content (markdown, CMS, WYSIWYG). It applies beautiful typography to h1-h6, p, ul, blockquote, pre, table. Sizes: prose-sm to prose-2xl. dark:prose-invert for dark mode. Essential for blogs and documentation.
Style and decoration
italic /* italic */ not-italic /* removes italic */ underline /* underline */ overline /* line above */ line-through /* strikethrough (old price) */ no-underline /* no decoration (links) */ /* Styled underline: */ class="underline underline-offset-4 decoration-blue-500 decoration-2" /* Uppercase/lowercase: */ uppercase | lowercase | capitalize | normal-case /* Discounted price: */ <span class="line-through text-gray-400">€99</span> <span class="font-bold text-green-600">€49</span>
italic/not-italic control the font style. underline, line-through (strikethrough — old prices), no-underline (removes the link underline). underline-offset-4 moves the underline away from the text. decoration-blue-500 decoration-2 changes color and thickness. uppercase for labels and badges.
Font family
font-sans /* ui-sans-serif, system-ui, -apple-system, ... */
font-serif /* ui-serif, Georgia, Cambria, ... */
font-mono /* ui-monospace, SFMono-Regular, Menlo, ... */
/* Custom (v3 config): */
fontFamily: { display: ['Clash Display', 'sans-serif'] }
/* Usage: class="font-display" */
/* v4 (@theme): */
--font-display: 'Clash Display', sans-serif;
/* Usage: */
<h1 class="font-display text-5xl font-bold">Title custom</h1>
<code class="font-mono text-sm bg-gray-100 px-2 py-1 rounded">npm install</code>font-sans (Tailwind default), font-serif, font-mono are the base families. For custom fonts, define them in fontFamily (v3) or --font-* (v4). font-mono for code, commands and technical data. Load the fonts via Google Fonts (<link>) or @font-face in the CSS. Always specify fallbacks.
Font variant and features
/* Numbers: */ tabular-nums /* monospaced numbers (tables) */ lining-nums /* aligned numbers */ oldstyle-nums /* old-style numbers (descenders) */ /* Variants: */ normal-nums ordinal /* 1st, 2nd, 3rd */ slashed-zero /* 0 with a slash (distinguishes from O) */ /* Font features (OpenType): */ [font-feature-settings:'ss01','cv01'] /* Table of values: */ <td class="tabular-nums text-right">€1.234,56</td> <td class="tabular-nums text-right">€99,00</td>
tabular-nums = numbers with a fixed width — essential for value tables, timers, counters and financial data (the digits line up in columns). ordinal for ordinal indicators (1st, 2nd). slashed-zero distinguishes 0 from O (codes, serials). font-feature-settings via an arbitrary value for advanced OpenType features.
Text alignment
text-left /* left (LTR default) */ text-center /* centered */ text-right /* right */ text-justify /* justified */ /* Responsive: */ class="text-center md:text-left" /* Vertical (inline/table-cell elements): */ align-baseline | align-top align-middle | align-bottom align-text-top | align-text-bottom /* Icon aligned with text: */ <span>Text <svg class="inline align-middle size-4">...</svg></span>
text-center for titles, CTAs and hero sections. text-left for body text (more readable). Responsive: text-center md:text-left = centered on mobile, left on desktop. align-* aligns inline elements vertically: align-middle for icons next to text. text-justify for text in narrow columns (with hyphens-auto).
Lists
list-none /* no marker (menus, nav) */
list-disc /* dots (ul default) */
list-decimal /* numbers (ol default) */
list-inside /* marker inside the padding */
list-outside /* marker outside (default) */
/* Styled list: */
<ul class="list-disc list-inside space-y-2 text-gray-700">
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>
/* Custom list (no marker, with icon): */
<ul class="space-y-2">
<li class="flex items-center gap-2">
<svg class="size-4 text-green-500">✓</svg> Item
</li>
</ul>list-none removes markers (menus, navigation). list-disc/list-decimal restore the default markers (preflight removes them). list-inside places the marker inside the padding. For custom lists with icons, use list-none + flex items-center gap-2 with an SVG. space-y-2 spaces the items.
Placeholder and selection
/* Placeholder: */ <input class="placeholder:text-gray-400 placeholder:italic" placeholder="Write here..."> /* Text selection: */ <p class="selection:bg-blue-200 selection:text-blue-900"> Text com selection customizada </p> /* Caret (input cursor): */ <input class="caret-blue-500"> /* Combination on an input: */ <input class="w-full px-4 py-2 rounded-lg border placeholder:text-gray-400 caret-blue-600 selection:bg-blue-100 focus:ring-2 focus:ring-blue-500">
placeholder:text-gray-400 styles the placeholder (the placeholder: variant). selection:bg-* changes the text selection color (a polish detail). caret-* changes the cursor color in the input. These are subtle details that improve the visual experience and show attention to detail. Combine with focus:ring-2 for a complete input.
Cores e Backgrounds
Text color
text-red-500 text-blue-600 text-green-700 text-gray-900 text-white text-black text-transparent /* Opacity: */ text-red-500/50 /* 50% opacity */ text-black/75 /* Arbitrary color: */ text-[#1fb6ff] text-[rgb(31,182,255)] /* Typical hierarchy: */ <h1 class="text-gray-900">Title</h1> <p class="text-gray-600">Corpo</p> <span class="text-gray-400">Secondary</span>
text-{color}-{shade} sets the text color. Scale from 50 (very light) to 950 (very dark). text-white/text-black for the extremes. /50 adds opacity (50%). text-transparent for invisible text (with bg-clip-text for gradients). Hierarchy: text-gray-900 (titles), text-gray-600 (body), text-gray-400 (secondary).
Opacity
opacity-0 /* invisible */ opacity-5 /* 5% */ opacity-25 /* 25% */ opacity-50 /* 50% */ opacity-75 /* 75% */ opacity-100 /* full (default) */ /* Color with alpha (/ syntax): */ bg-black/50 /* black background 50% */ text-white/75 /* white text 75% */ border-gray-200/50 /* Hover with opacity: */ class="opacity-70 hover:opacity-100 transition-opacity" /* Modal overlay: */ <div class="fixed inset-0 bg-black/50 backdrop-blur-sm"></div>
opacity-* controls the transparency of the WHOLE element (including children). /50 on the color adds alpha only to the color (without affecting children). bg-black/50 for modal overlays. opacity-70 hover:opacity-100 + transition-opacity for a subtle hover effect on images and icons. Prefer /alpha on the color when you want transparency without affecting the content.
Outline
outline-none /* removes outline */ outline /* solid outline */ outline-dashed outline-dotted outline-2 /* thickness */ outline-blue-500 /* color */ outline-offset-2 /* distance from the element */ /* Accessibility (focus visible with keyboard only): */ <button class="focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2"> /* NEVER: focus:outline-none without an alternative */
outline-* styles the outline (it doesn't affect the layout, unlike border). focus-visible:outline shows only when navigating with the keyboard (better than focus:outline-none which removes accessibility). outline-offset-2 moves the outline away from the element. NEVER remove the focus without an alternative — use focus:ring-2 or focus-visible:outline to keep accessibility.
Background color
bg-blue-500
bg-gray-100
bg-white
bg-black
bg-transparent
bg-inherit
bg-current /* current text color */
/* Opacity: */
bg-blue-500/75
bg-black/50
/* Arbitrary color: */
bg-[#1fb6ff]
/* Background image: */
bg-[url('/img/pattern.png')]
/* Dark overlay over an image: */
<div class="bg-black/50 absolute inset-0"></div>bg-{color}-{shade} sets the background. bg-white/bg-gray-100 for light backgrounds. /75 = 75% opacity (overlays). bg-current uses the text color (useful for SVG icons that inherit the color). bg-[url(...)] for arbitrary background images. bg-black/50 for dark overlays over images (improves text readability).
Border color
border-gray-300 border-blue-500 border-red-200 border-transparent border-current /* text color */ border-inherit /* Specific sides: */ border-t-gray-200 /* top only */ border-b-blue-500 /* bottom only */ /* Opacity: */ border-gray-300/50 /* Example: card with a subtle border */ <div class="border border-gray-200 rounded-lg p-4"> /* Separator: */ <hr class="border-t border-gray-200">
border-{color} sets the border color. border-t-*/border-b-* for specific sides (separators, underlines). border-transparent for invisible borders (keeps space for hover). border-current uses the text color (icons with a border). Combine with border (1px) or border-2 for thickness. border-gray-200 is the default for subtle borders.
Background image and size
bg-cover /* fills (crops the excess) */
bg-contain /* fits everything (may have bars) */
bg-auto /* original size */
bg-center /* centered */
bg-top, bg-bottom, bg-left, bg-right
bg-no-repeat /* no repeat */
bg-fixed /* parallax (fixed to scroll) */
bg-local /* scrolls with content */
/* Hero with a background image: */
<div class="bg-cover bg-center bg-no-repeat h-96"
style="background-image: url('hero.jpg')">
<div class="bg-black/50 h-full flex items-center">
<h1 class="text-white text-4xl">Title</h1>
</div>
</div>bg-cover + bg-center = background image that fills without distorting (crops the excess). bg-no-repeat avoids repetition. bg-fixed for a parallax effect. Set the image via inline style or bg-[url(...)]. Combine with a bg-black/50 overlay for text readability over the image. An essential pattern for hero sections.
Default color palette
/* Neutrals (5 options): */ slate (bluish) | gray (neutral) | zinc (warm) neutral (pure) | stone (earthy) /* Colors (17): */ red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose /* Scale per color (11 shades): */ 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950 /* Total: 22 colors × 11 shades = 242 colors */
22 colors × 11 shades = 242 available colors. Neutrals: slate (bluish, modern), gray (neutral, versatile), zinc (warm), stone (earthy). Scale: 50 (very light, backgrounds) → 500 (base, actions) → 950 (very dark, text). Use 500/600 for buttons, 100/200 for backgrounds, 700/900 for text.
Ring (focus ring)
ring-1, ring-2, ring-4, ring-8, ring ring-blue-500 ring-offset-2 ring-offset-white /* Focus state (accessibility): */ <button class="focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"> /* Decorative ring (avatar): */ <img class="ring-4 ring-blue-300 rounded-full" src="avatar.jpg"> /* Ring on hover: */ <div class="hover:ring-2 hover:ring-blue-400 transition">
ring-* creates a ring (via box-shadow) around the element — it doesn't affect the layout. ring-offset-2 adds space between the element and the ring. Essential for focus:ring-2 (accessibility — replaces the default outline). ring-4 ring-blue-300 for highlighted avatars. Combine with ring-offset-white on dark backgrounds.
bg-clip and mix-blend
/* Clip (background clipping): */ bg-clip-border bg-clip-padding bg-clip-content bg-clip-text /* background visible only on the text */ /* Text with gradient: */ <h1 class="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">Title com gradiente</h1> /* Blend modes: */ mix-blend-multiply /* darkens */ mix-blend-screen /* lightens */ mix-blend-overlay /* contrasts */ mix-blend-difference /* inverts */ /* Creative overlay: */ <div class="bg-blue-600 mix-blend-multiply">
bg-clip-text + text-transparent = text with a gradient or background image (very popular in hero sections). mix-blend-* applies blend modes (like Photoshop): multiply darkens, screen lightens, overlay contrasts. Useful for creative overlays, image effects and advanced visual compositions.
Gradients
<div class="bg-gradient-to-r from-blue-500 to-purple-600"> /* Directions: */ bg-gradient-to-r /* → right */ bg-gradient-to-l /* ← left */ bg-gradient-to-t /* ↑ up */ bg-gradient-to-b /* ↓ down */ bg-gradient-to-tr /* ↗ diagonal */ bg-gradient-to-br /* ↘ diagonal */ /* Intermediate stop: */ class="bg-gradient-to-r from-cyan-500 via-blue-500 to-purple-600" /* Text gradient: */ class="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent"
bg-gradient-to-* sets the gradient direction. from-* = start color, to-* = end color, via-* = intermediate stop (3+ colors). For gradient text: bg-clip-text text-transparent + gradient on the background. Combine with hover:from-* to animate the start color on hover. Gradients are popular in hero sections and CTA buttons.
Divide (separators between children)
divide-y /* horizontal line between children */ divide-x /* vertical line between children */ divide-y-2 /* 2px thickness */ divide-gray-200 /* color */ divide-dashed /* dashed */ divide-y-reverse /* List with separators: */ <ul class="divide-y divide-gray-200"> <li class="py-3">Item 1</li> <li class="py-3">Item 2</li> <li class="py-3">Item 3</li> </ul> /* Advantage: no border on the last item */
divide-y adds borders between children (without touching the first/last). divide-gray-200 sets the color. divide-dashed for dashed. It replaces border-b on each item (cleaner, no border on the last one). Ideal for lists, feeds, tables and any vertical stack with separators. divide-x for vertical separators (side-by-side buttons).
Semantic colors (states)
/* Success: */ class="bg-green-100 text-green-800 border-green-200" /* Error: */ class="bg-red-100 text-red-800 border-red-200" /* Warning: */ class="bg-amber-100 text-amber-800 border-amber-200" /* Info: */ class="bg-blue-100 text-blue-800 border-blue-200" /* Neutral: */ class="bg-gray-100 text-gray-800 border-gray-200" /* Pattern: light background (100) + dark text (800) + border (200) */
Semantic colors for states: green (success), red (error), amber (warning), blue (info), gray (neutral). Consistent pattern: light background (100) + dark text (800) + subtle border (200). It ensures a readable contrast without being aggressive. Use it in alerts, badges, toasts and validation messages.
Borders and Effects
Border (style and thickness)
border /* 1px all sides */ border-2 /* 2px */ border-4 /* 4px */ border-8 /* 8px */ border-0 /* removes */ border-t /* top only (1px) */ border-b-2 /* bottom only (2px) */ border-x /* left + right */ border-y /* top + bottom */ /* Style: */ border-solid | border-dashed | border-dotted border-double | border-none /* Card with a subtle border: */ <div class="border border-gray-200 rounded-lg p-4">
border = 1px solid on all sides (the most used). border-2/border-4 for more thickness. border-t/border-b for specific sides (separators, underlines). border-dashed/border-dotted for alternative styles. Combine with border-gray-200 for a subtle color. preflight removes default borders — add them explicitly.
Transforms
/* Scale: */ scale-105, scale-95, scale-0, scale-150 scale-x-50, scale-y-110 /* Rotation: */ rotate-45, rotate-90, rotate-180 -rotate-12 /* Translation: */ translate-x-4, -translate-y-2 translate-x-1/2, -translate-x-1/2 /* Skew: */ skew-x-3, -skew-y-6 /* Hover (card zoom): */ class="hover:scale-105 transition-transform duration-300" /* Rotating icon: */ class="group-hover:rotate-90 transition-transform"
scale-105 = 105% (card hover, subtle zoom). rotate-45 = 45° (arrows, icons). translate-x-1/2 = shifts 50% (centering with absolute). skew-* for slanted effects. Negative values with -. Combine with transition-transform to animate. hover:scale-110 = zoom on hover (images, cards).
Cursor and resize
cursor-pointer /* hand (links/buttons) */ cursor-default /* normal arrow */ cursor-not-allowed /* forbidden (disabled) */ cursor-wait /* hourglass (loading) */ cursor-text /* text (inputs) */ cursor-move /* move (drag) */ cursor-grab /* grab */ cursor-grabbing /* dragging */ cursor-zoom-in cursor-crosshair /* Resize (textareas): */ resize-none | resize | resize-x | resize-y /* Disabled button: */ <button disabled class="cursor-not-allowed opacity-50">
cursor-pointer for clickable elements (Tailwind removes it from the button by default in preflight). cursor-not-allowed for disabled states. cursor-grab/grabbing for drag & drop. resize-none removes the resize from textareas (default in custom UI). resize-y only allows vertical resize. Combine with disabled:opacity-50.
Border radius
rounded-none /* 0 (sharp) */ rounded-sm /* 0.125rem (2px) */ rounded /* 0.25rem (4px) — default */ rounded-md /* 0.375rem (6px) */ rounded-lg /* 0.5rem (8px) */ rounded-xl /* 0.75rem (12px) */ rounded-2xl /* 1rem (16px) */ rounded-3xl /* 1.5rem (24px) */ rounded-full /* 9999px (circle/pill) */ /* Specific sides: */ rounded-t-lg /* top only */ rounded-b-none /* removes bottom */ rounded-l-full /* left only */ rounded-tr-xl /* top-right corner only */
rounded = 0.25rem (default). rounded-lg/rounded-xl for modern cards. rounded-full = circle (avatars) or pill (buttons, badges). rounded-t-*/rounded-b-* for specific sides (cards with an image on top). rounded-none removes it (sharp/brutalist style). rounded-tr-xl for individual corners.
Animations
animate-spin /* continuous rotation (loading) */ animate-ping /* pulses and expands (notification) */ animate-pulse /* pulses opacity (skeleton) */ animate-bounce /* bounces (attention) */ animate-none /* removes animation */ /* Loading spinner: */ <span class="animate-spin inline-block size-6 border-4 border-gray-300 border-t-blue-600 rounded-full"></span> /* Notification (pulsing dot): */ <span class="relative"> <span class="animate-ping absolute size-3 bg-red-500 rounded-full"></span> <span class="relative size-3 bg-red-500 rounded-full"></span> </span>
animate-spin for loading spinners. animate-ping for notifications (pulses and expands — combine with a static dot on top). animate-pulse for skeleton loading (gray blocks that pulse). animate-bounce to draw attention (scroll arrows). Spinner: border-4 border-t-blue-600 rounded-full animate-spin. For custom animations, use @keyframes in the CSS.
Scroll behavior and snap
/* Smooth scroll: */ scroll-smooth /* smooth scroll (anchors) */ scroll-auto /* instant */ /* Scroll snap: */ snap-x /* horizontal snap */ snap-y /* vertical snap */ snap-mandatory /* mandatory */ snap-proximity /* by proximity */ snap-start, snap-center, snap-end snap-none /* Native carousel (no JS): */ <div class="flex overflow-x-auto snap-x snap-mandatory"> <div class="snap-center shrink-0 w-full">Slide 1</div> <div class="snap-center shrink-0 w-full">Slide 2</div> <div class="snap-center shrink-0 w-full">Slide 3</div> </div>
scroll-smooth on the html for smooth scrolling on anchors (#section links). snap-x snap-mandatory + snap-center on the children = a native carousel without JavaScript. snap-proximity = snap only when close. shrink-0 prevents the slides from shrinking. A modern, performant alternative to slider libraries (Swiper, Slick).
Shadows (box-shadow)
shadow-sm /* subtle (flat cards) */ shadow /* default */ shadow-md /* medium (dropdowns) */ shadow-lg /* large (modals) */ shadow-xl /* extra */ shadow-2xl /* maximum (hero) */ shadow-inner /* inner (pressed inputs) */ shadow-none /* removes */ /* Shadow color: */ shadow-blue-500/50 shadow-lg shadow-blue-500/25 /* Hover (elevation): */ class="shadow hover:shadow-lg transition-shadow duration-300"
shadow = default shadow. shadow-sm subtle (flat cards), shadow-lg pronounced (dropdowns, modals). shadow-inner = inset (pressed inputs, wells). Color: shadow-blue-500/25 for colored shadows. Hover: hover:shadow-lg transition-shadow for an elevation effect (the card "rises"). Essential for depth and visual hierarchy.
Filters (image)
/* Blur: */ blur-sm, blur, blur-md, blur-lg, blur-xl /* Brightness/contrast: */ brightness-50, brightness-100, brightness-150 contrast-50, contrast-100, contrast-200 /* Saturation: */ saturate-0, saturate-100, saturate-200 /* Grayscale/Sepia/Invert: */ grayscale, sepia, invert, hue-rotate-90 /* Hover (team photo): */ class="grayscale hover:grayscale-0 transition duration-300" /* Darkened image: */ class="brightness-75 hover:brightness-100 transition"
blur-* blurs (overlays, modal backgrounds). brightness-50 darkens, brightness-150 lightens. grayscale + hover:grayscale-0 = classic effect on team photos (gray → color on hover). saturate-0 = no color. Combine with transition to animate the filters on hover. Essential for galleries and portfolios.
Will-change and performance
will-change-auto
will-change-scroll /* scroll-position */
will-change-contents /* contents */
will-change-transform /* transform */
/* GPU acceleration: */
transform-gpu /* translateZ(0) — forces a GPU layer */
transform-none
/* Backface (3D flip cards): */
backface-visible
backface-hidden /* hides the back */
/* Flip card: */
<div class="[perspective:1000px]">
<div class="transition-transform duration-500
[transform-style:preserve-3d] hover:[transform:rotateY(180deg)]">
<div class="[backface-visibility:hidden]">Frente</div>
<div class="[transform:rotateY(180deg)] [backface-visibility:hidden]">Verso</div>
</div>
</div>will-change-transform tells the browser to optimize (creates a GPU layer). Use it sparingly — only on elements that will animate. transform-gpu forces GPU acceleration. backface-hidden for 3D flip cards (hides the back when rotating). Remove will-change after the animation to free memory. A flip card uses perspective + preserve-3d + rotateY.
Transitions
transition /* common properties (color, shadow, transform) */ transition-all /* all properties */ transition-colors /* colors (bg, text, border) */ transition-transform /* transform only */ transition-opacity transition-shadow transition-none /* Duration and easing: */ duration-150, duration-200, duration-300, duration-500 ease-linear, ease-in, ease-out, ease-in-out /* Button with a smooth transition: */ <button class="transition-colors duration-200 hover:bg-blue-700 active:bg-blue-800">
transition animates common properties (colors, shadows, transforms, opacity). transition-colors = colors only (more performant than transition-all). duration-200 = 200ms (ideal for UI). ease-in-out = smooth acceleration. Always combine with a state (hover:, focus:). Without transition, the change is instant (no animation).
Backdrop filter
backdrop-blur-sm backdrop-blur backdrop-blur-md backdrop-blur-lg backdrop-blur-xl backdrop-brightness-50 backdrop-saturate-150 /* Navbar with frosted glass (glassmorphism): */ <nav class="fixed top-0 w-full bg-white/70 backdrop-blur-md border-b border-gray-200/50 z-50"> Menu </nav> /* Modal overlay: */ <div class="fixed inset-0 bg-black/30 backdrop-blur-sm">
backdrop-blur-* blurs the content BEHIND the element (frosted glass / glassmorphism effect). Combine with bg-white/70 (semi-transparent background) — without opacity, the blur isn't visible. backdrop-brightness-50 darkens the background. Popular in navbars, modals and modern overlays. Requires a bg-* with opacity for the effect to work.
Glassmorphism effect
/* Frosted glass card: */ <div class="bg-white/20 backdrop-blur-lg border border-white/30 rounded-2xl shadow-xl p-6"> Content glassmorphism </div> /* Glass navbar: */ <nav class="bg-white/70 backdrop-blur-md border-b border-gray-200/50 shadow-sm"> /* Dark mode glass: */ <div class="bg-gray-900/50 backdrop-blur-xl border border-white/10 rounded-xl"> /* Requires: bg with opacity + backdrop-blur + subtle border */
Glassmorphism (frosted glass): combine bg-white/20 (semi-transparent background) + backdrop-blur-lg (blur) + border border-white/30 (subtle border) + rounded-2xl shadow-xl. It works over images or colored gradients. In dark mode: bg-gray-900/50 + border-white/10. Popular in navbars, cards and modern overlays (macOS/iOS style).
Responsive and States
Breakpoints
sm: /* ≥ 640px (landscape phone) */ md: /* ≥ 768px (tablet) */ lg: /* ≥ 1024px (small desktop) */ xl: /* ≥ 1280px (desktop) */ 2xl: /* ≥ 1536px (large screen) */ /* Mobile-first: */ class="w-full md:w-1/2 lg:w-1/3" /* The default is mobile-first: */ /* base = mobile (all sizes) */ /* prefix = overrides from that breakpoint up */ /* There is NO xs prefix (base = mobile) */
Mobile-first: classes without a prefix apply at ALL sizes; with a prefix, they override from that breakpoint up. w-full md:w-1/2 = 100% on mobile, 50% on md+. There is no xs prefix — the base IS mobile. The breakpoints are configurable in the theme (v3) or via --breakpoint-* (v4). Think "from" and not "up to".
group (parent controls children)
<div class="group cursor-pointer">
<h3 class="text-gray-900 group-hover:text-blue-600
transition-colors">
Title
</h3>
<p class="text-gray-500 group-hover:text-gray-700">
Description
</p>
<span class="opacity-0 group-hover:opacity-100
transition-opacity">
→ See more
</span>
</div>
/* group-focus, group-active, group-hover */
/* Named: group/card + group-hover/card: */group on the parent + group-hover:* on the child = the child reacts to the PARENT's hover. Useful for cards: hovering the card changes the title color, shows an arrow, raises the shadow. group-focus:, group-active: also work. For nested groups, name them: group/card + group-hover/card: (avoids conflicts). Essential for interactive components.
Container queries (v3.2+)
/* Plugin: @tailwindcss/container-queries */
<div class="@container">
<div class="grid grid-cols-1 @lg:grid-cols-3">
<div>Sidebar</div>
<div class="@lg:col-span-2">Content</div>
</div>
</div>
/* Breakpoints: @sm, @md, @lg, @xl, @2xl */
/* Based on the PARENT's width, not the viewport */
/* v4 (native, no plugin): */
@container
@min-[400px]:flex-row
/* Named: @container/sidebar + @lg/sidebar: */@container on the parent + @lg:* on the child = styles based on the CONTAINER's width (not the viewport). Ideal for reusable components: a card in the sidebar (narrow) vs. in the main (wide) adapts automatically. @sm, @md, @lg are the breakpoints. In v4, container queries are native (no plugin). Name them with @container/name for multiple containers.
Custom breakpoints
/* v3 (config): */
theme: {
screens: {
'xs': '475px',
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1536px',
'3xl': '1920px',
}
}
/* v4 (CSS): */
@theme {
--breakpoint-xs: 475px;
--breakpoint-3xl: 1920px;
}
/* Usage: xs:text-sm, 3xl:text-2xl */Custom breakpoints: add xs (475px) for small phones or 3xl (1920px) for large screens. In v3, define them in theme.screens (replaces) or theme.extend.screens (adds). In v4, use --breakpoint-* in @theme. Usage: xs:text-sm, 3xl:text-2xl. Keep the defaults and add only what's needed.
States (hover/focus/active)
<button class="bg-blue-500 text-white px-4 py-2 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 active:bg-blue-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"> Click </button> /* Available states: */ hover: | focus: | focus-within: | focus-visible: active: | visited: | disabled: | checked: first: | last: | odd: | even: | empty: required: | valid: | invalid: | read-only:
hover: = on mouse over. focus:ring-2 = accessibility (keyboard navigation). active: = on click/press. disabled:opacity-50 = disabled state. focus-visible: = only when navigating with the keyboard (better UX than focus:). Always use transition to smooth the state changes. Never remove the focus without an alternative.
peer (previous sibling)
/* Custom toggle: */
<input class="peer sr-only" type="checkbox" id="toggle">
<label for="toggle"
class="block w-12 h-6 bg-gray-300 rounded-full
peer-checked:bg-blue-600 transition-colors">
</label>
/* Validation: */
<input class="peer" type="email">
<p class="hidden peer-invalid:block text-red-500">Invalid</p>
/* Named (multiple peers): */
<input class="peer/input">
<span class="peer-checked/input:text-blue-600">
/* The peer MUST come BEFORE the styled element in the DOM */peer on the previous element + peer-checked:* on the next one = the next one reacts to the previous element's state. Essential for custom toggles (checkbox + label), visual validation (peer-invalid:), and inputs with floating labels. The peer must come BEFORE the styled element in the DOM. For multiple peers, name them: peer/input + peer-checked/input:.
RTL and logical directions
/* Logical (RTL-aware): */ ms-4 /* margin-start (left in LTR, right in RTL) */ me-4 /* margin-end */ ps-4 /* padding-start */ pe-4 /* padding-end */ start-0 /* left in LTR, right in RTL */ end-0 /* right in LTR, left in RTL */ text-start | text-end /* Physical (fixed): */ ml-4, mr-4, pl-4, pr-4, left-0, right-0 text-left, text-right /* RTL variant: */ rtl:space-x-reverse rtl:text-right
Logical properties (ms, me, ps, pe, start, end) adapt automatically to RTL (Arabic, Hebrew). Prefer logical ones in new code for internationalization. rtl:space-x-reverse reverses the spacing in RTL. text-start/text-end replace text-left/text-right. Essential for multi-language apps.
Dark mode
/* v3 config: */
darkMode: 'class' /* or 'average' (default) */
/* v4: default = average; for class: */
@custom-variant dark (&:where(.dark, .dark *));
/* Usage (light/dark pairs): */
<div class="bg-white text-gray-900
dark:bg-gray-900 dark:text-white
dark:border-gray-700">
Content
</div>
/* Toggle via JS: */
document.documentElement.classList.toggle('dark')
/* Always define PAIRS: bg-white dark:bg-gray-900 */dark: applies styles in the dark theme. darkMode: 'class' (v3) = controlled by the .dark class on the html. 'average' (default) = follows the operating system's prefers-color-scheme. In v4, use @custom-variant for class mode. Always define pairs: bg-white dark:bg-gray-900. Include borders: border-gray-200 dark:border-gray-700.
Child and position variants
/* First/last child: */ first:pt-0 last:pb-0 last:border-0 /* Odd/even (alternating rows): */ odd:bg-gray-50 even:bg-white /* Only child: */ only:rounded-lg /* Empty: */ empty:hidden /* List with separators and adjusted padding: */ <ul class="divide-y divide-gray-200"> <li class="py-3 first:pt-0 last:pb-0">Item</li> </ul> /* Table with alternating rows: */ <tr class="odd:bg-gray-50 even:bg-white">
first:, last:, odd:, even:, only:, empty: are position/state variants of the element. first:pt-0 last:pb-0 removes extra padding at the start/end of lists. odd:bg-gray-50 = alternating rows (tables). last:border-0 removes the border from the last item. empty:hidden hides empty elements.
Print and motion
/* Print: */ print:hidden /* hidden when printing */ print:block /* visible only when printing */ print:text-black /* black text when printing */ print:shadow-none /* no shadows */ /* Motion (respects user preferences): */ motion-safe:animate-bounce motion-reduce:transition-none motion-reduce:animate-none /* Button with accessible animation: */ <button class="motion-safe:hover:scale-105 motion-reduce:hover:scale-100 transition"> /* Respects the OS prefers-reduced-motion */
print:hidden hides elements when printing (nav, buttons, sidebars). print:text-black forces black text (saves ink). motion-safe: applies animations only if the user didn't request reduced motion. motion-reduce: removes animations for those who prefer it. Essential for accessibility (vestibular disorders, epilepsy). Respects prefers-reduced-motion.
Show/hide responsive
/* Hide on mobile, show on md+: */ hidden md:block /* Show only on mobile: */ block md:hidden /* Visibility (keeps space): */ visible | invisible /* Desktop menu + mobile hamburger: */ <nav class="hidden md:flex gap-4"> <a href="#">Home</a> <a href="#">About</a> </nav> <button class="md:hidden">☰</button> /* Sidebar: hidden on mobile */ <aside class="hidden lg:block w-64">Sidebar</aside>
hidden md:block = hidden on mobile, visible on md+. block md:hidden = visible only on mobile. hidden = display: none (removed from the flow, takes no space). invisible = visibility: hidden (keeps space). Classic pattern: nav hidden md:flex + hamburger button md:hidden. Essential for responsive navigation.
Data and ARIA variants
/* Data attributes: */ data-[state=open]:block data-[active=true]:text-blue-600 data-[side=top]:animate-in /* ARIA: */ aria-selected:bg-blue-100 aria-expanded:rotate-180 aria-hidden:true:invisible aria-invalid:border-red-500 aria-current:font-bold /* Accordion (icon rotates when expanded): */ <button aria-expanded="false" class="aria-expanded:rotate-180 transition-transform"> ▼ </button>
data-[state=open]: styles based on data-* attributes (popular in Radix UI, Headless UI, Alpine.js). aria-expanded:rotate-180 rotates the icon when expanded (accordions). aria-invalid:border-red-500 for accessible validation. aria-current:font-bold for active navigation. Essential for accessible components with states managed by JavaScript.
Complete responsive (example)
<section class="py-8 md:py-16 lg:py-24">
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
<h1 class="text-2xl md:text-4xl lg:text-5xl
font-bold text-center md:text-left">
Title
</h1>
<p class="mt-4 text-base md:text-lg text-gray-600
max-w-prose mx-auto md:mx-0">
Description...
</p>
<div class="grid grid-cols-1 md:grid-cols-2
lg:grid-cols-3 gap-4 md:gap-8 mt-8 md:mt-12">
<div class="p-4 md:p-6 bg-white rounded-xl shadow">Card</div>
</div>
</div>
</section>Complete responsive pattern: progressive padding (py-8 md:py-16 lg:py-24), scalable typography (text-2xl md:text-4xl), adaptive grid (grid-cols-1 md:grid-cols-2 lg:grid-cols-3), responsive gap (gap-4 md:gap-8), adaptable alignment (text-center md:text-left). Mobile-first: define the mobile version and override on larger breakpoints.
Advanced and Good Practices
@apply (CSS components)
/* input.css */
@layer components {
.btn {
@apply px-4 py-2 rounded-lg font-medium
transition-colors duration-200;
}
.btn-primary {
@apply btn bg-blue-600 text-white
hover:bg-blue-700;
}
.btn-outline {
@apply btn border-2 border-blue-600 text-blue-600
hover:bg-blue-50;
}
}
/* Usage: */
<button class="btn-primary">Ok</button>@apply extracts reusable classes into CSS. @layer components ensures the correct order (doesn't override utilities). Useful for buttons, inputs and repeated patterns in content without components (Markdown, CMS, emails). BUT: prefer framework components (React/Vue/Blade) for reuse. @apply is for when you need classes in plain CSS.
tailwind-merge and clsx
import { twMerge } from 'tailwind-merge'
import { clsx } from 'clsx'
/* twMerge resolves conflicts: */
twMerge('px-4 py-2', 'px-6') /* → 'py-2 px-6' */
twMerge('text-red-500', 'text-blue-500') /* → 'text-blue-500' */
/* conditional clsx: */
clsx('btn', { 'btn-active': isActive })
/* cn() = clsx + twMerge (shadcn/ui pattern): */
function cn(...inputs) {
return twMerge(clsx(inputs))
}
<div class={cn('p-4', isActive && 'bg-blue-50', className)}>tailwind-merge resolves conflicts: twMerge('px-4', 'px-6') = px-6 (the last one wins, no duplicates). clsx composes classes conditionally. cn() = a combination of the two (the standard in shadcn/ui). Essential for components that accept a className prop and need conflict-free merging. Without this, px-4 px-6 produces unpredictable behavior.
Debug and troubleshooting
/* 1. Class not working? Check the content: */ content: ["./resources/views/**/*.blade.php"] /* 2. Inspect with DevTools: */ /* - Is the class in the generated CSS? */ /* - Is there a specificity conflict? */ /* - Is another class overriding it? */ /* 3. Use the Debug Screens plugin: */ /* Shows the active breakpoint in the corner */ npm install -D @tailwindcss/debug-screens /* 4. Important (last resort): */ class="!text-red-500" /* !important */ /* 5. Order: the LAST class in the CSS wins */ /* (not the last one in the HTML) */
Troubleshooting: 1) Class not showing? Check the content in the config (the file may not be included). 2) Inspect with DevTools — is the class in the generated CSS? Is there a specificity conflict? 3) @tailwindcss/debug-screens shows the active breakpoint. 4) The ! prefix adds !important (last resort). 5) The ORDER in the CSS matters, not the order in the HTML — the last generated class wins.
@layer and @utility (v4)
/* v4: create custom utilities */
@utility scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar { display: none; }
}
@utility text-shadow {
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
/* Usage (works with variants!): */
<div class="scrollbar-hide hover:text-shadow md:text-shadow">
/* v3 (@layer): */
@layer utilities {
.scrollbar-hide { ... }
}
/* Layers: base < components < utilities */In v4, @utility creates custom utilities that work with variants (hover:scrollbar-hide, md:text-shadow). In v3, use @layer utilities (but without automatic variants). @layer components for composite classes. @layer base for reset/global styles. The layer order matters: base < components < utilities.
Blade components (Laravel)
{{-- resources/views/components/button.blade.php --}}
@props(['variant' => 'primary', 'size' => 'md'])
@php
$base = 'inline-flex items-center gap-2 rounded-lg
font-medium transition-colors focus:ring-2';
$variants = [
'primary' => 'bg-blue-600 text-white hover:bg-blue-700',
'outline' => 'border-2 border-blue-600 text-blue-600',
];
$sizes = ['sm' => 'px-3 py-1.5 text-sm',
'md' => 'px-4 py-2', 'lg' => 'px-6 py-3 text-lg'];
@endphp
<button {{ $attributes->class([$base,
$variants[$variant], $sizes[$size]]) }}>
{{ $slot }}
</button>
{{-- Usage: <x-button variant="outline" size="lg">Ok</x-button> --}}Reusable Blade components: define $base + variant arrays. Use $attributes->class([]) to merge classes (Laravel merges automatically). Props with defaults: @props(['variant' => 'primary']). Usage: <x-button variant="outline" size="lg">. An alternative to @apply — more flexible and with variant support. The recommended pattern in Laravel.
Performance and production
/* 1. Tree-shaking (automatic): */ /* Only classes used in the HTML are generated */ /* Large project: ~10-30kb of final CSS */ /* 2. PurgeCSS (v3 — automatic via content) */ /* 3. Minification: */ npx tailwindcss -i input.css -o output.css --minify /* 4. Avoid transition-all (specific is better): */ transition-colors /* good */ transition-all /* heavy (all props) */ /* 5. will-change sparingly */ /* 6. Lazy load images: */ <img loading="lazy" class="..." src="photo.jpg">
Performance: 1) automatic tree-shaking — only used classes are generated (10-30kb final). 2) Minify with --minify or vite build. 3) Prefer transition-colors over transition-all (fewer animated properties = better performance). 4) will-change sparingly (creates GPU layers). 5) loading="lazy" on images below the fold. 6) Avoid excessive @apply (generates duplicated CSS).
Best practices
/* 1. Mobile-first: base → sm → md → lg */
class="flex-col md:flex-row"
/* 2. Components in the framework (not @apply) */
<Button variant="primary" size="lg" />
/* 3. Prettier to sort classes */
prettier-plugin-tailwindcss
/* 4. Avoid duplicate/conflicting classes */
/* Bad: class="p-4 p-2" */
/* 5. Use variants instead of custom CSS */
hover:bg-blue-700 (not .btn:hover { })
/* 6. max-w-prose for long text */
<article class="max-w-prose">
/* 7. gap instead of space-x/y */
class="flex gap-4"Best practices: 1) Always mobile-first (base → larger breakpoints). 2) Extract components in the framework (React/Vue/Blade) instead of @apply. 3) Use prettier-plugin-tailwindcss for consistent order. 4) Avoid duplicate/conflicting classes. 5) Prefer variants (hover:) over custom CSS. 6) max-w-prose for readability. 7) gap instead of space-*.
Custom animations (@keyframes)
/* v4 (CSS): */
@theme {
--animate-fade-in: fade-in 0.3s ease-out;
--animate-slide-up: slide-up 0.5s ease-out;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slide-up {
from { opacity: 0; transform: translateY(1rem); }
to { opacity: 1; transform: translateY(0); }
}
/* Usage: class="animate-fade-in" */
/* v3 (config): */
theme: { extend: { animation: {
'fade-in': 'fade-in 0.3s ease-out' },
keyframes: { 'fade-in': { ... } } } }Custom animations: define @keyframes + a --animate-* variable (v4) or theme.extend.animation + keyframes (v3). Usage: animate-fade-in, animate-slide-up. Works with variants: hover:animate-fade-in. For complex animations (stagger, spring), use libraries like framer-motion or @tailwindcss/animate.
CVA (Class Variance Authority)
import { cva } from 'class-variance-authority'
const button = cva(
'px-4 py-2 rounded-lg font-medium transition',
{
variants: {
variant: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
outline: 'border-2 border-blue-600 text-blue-600',
ghost: 'hover:bg-gray-100',
},
size: {
sm: 'px-3 py-1.5 text-sm',
lg: 'px-6 py-3 text-lg',
},
},
defaultVariants: { variant: 'primary', size: 'sm' },
}
)
<button class={button({ variant: 'outline', size: 'lg' })}>CVA (class-variance-authority) manages class variants in React components. Define base classes + variants (variant, size) + defaults. It generates the correct classes without conflicts. Popular in React + Tailwind (shadcn/ui uses CVA). Alternative: clsx + tailwind-merge for manual composition. Essential for design systems with multiple variants.
sr-only and accessibility
/* Text visible only to screen readers: */ <a href="#content" class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:px-4 focus:py-2 focus:bg-white focus:rounded-lg focus:shadow"> Skip to content </a> /* sr-only: */ /* position: absolute; width: 1px; height: 1px; */ /* padding: 0; margin: -1px; overflow: hidden; */ /* clip: rect(0,0,0,0); border: 0; */ /* Accessible label (no visual): */ <label class="sr-only" for="search">Search</label> <input id="search" class="...">
sr-only hides visually but keeps it accessible to screen readers (position absolute, 1px, clip). Essential for: labels of icon-only inputs, "skip links" (sr-only focus:not-sr-only = visible only when focused with the keyboard), descriptive text. focus:not-sr-only makes it visible on focus (skip navigation). NEVER use hidden or display: none for accessible content.
Flexbox
Enable flex and direction
<div class="flex">Contentor flex (line horizontal)</div> <div class="inline-flex">Flex com display inline</div> /* Direction: */ flex-row /* → horizontal (default) */ flex-row-reverse /* ← reversed horizontal */ flex-col /* ↓ vertical (column) */ flex-col-reverse /* ↑ reversed vertical */ /* Most used responsive pattern: */ class="flex flex-col md:flex-row" /* Stacks on mobile, side by side on md+ */
flex makes the element a flex container — the children sit side by side by default. flex-row (default) = horizontal line; flex-col = vertical column. The most used mobile-first pattern: flex-col md:flex-row = stacks on mobile, side by side on md+. inline-flex = flex with inline display (for buttons with an icon).
flex-wrap
flex-wrap /* allows line wrapping */ flex-nowrap /* no wrap (default) */ flex-wrap-reverse /* reversed wrap */ /* List of tags/chips: */ <div class="flex flex-wrap gap-2"> <span class="px-3 py-1 bg-gray-100 rounded-full text-sm">Tag 1</span> <span class="px-3 py-1 bg-gray-100 rounded-full text-sm">Tag 2</span> <span class="px-3 py-1 bg-gray-100 rounded-full text-sm">Tag 3</span> <span class="px-3 py-1 bg-gray-100 rounded-full text-sm">Tag 4</span> </div> /* Without wrap: items shrink or overflow */
flex-wrap lets items move to the next line when they don't fit horizontally. flex-nowrap (default) forces everything on one line (items can shrink or overflow). Essential for lists of tags, chips, filters, toolbar buttons. Combine with gap-2 for consistent spacing between items across all lines.
align-content (multi-line)
/* Requires flex-wrap + a defined height to have effect */ content-start content-center content-between content-around content-evenly content-stretch /* default */ <div class="flex flex-wrap content-center h-64 gap-4"> <div class="w-1/3 p-4 bg-gray-100">A</div> <div class="w-1/3 p-4 bg-gray-100">B</div> <div class="w-1/3 p-4 bg-gray-100">C</div> <div class="w-1/3 p-4 bg-gray-100">D</div> </div>
content-* aligns the LINES when there is flex-wrap and multiple lines. Without wrap or with a single line, it has no effect. content-center = lines centered vertically in the container. content-between = first line at the top, last at the bottom. Useful for card grids with a fixed height where you want to distribute the lines evenly.
Navbar with flex
<nav class="flex items-center justify-between px-6 h-16">
<!-- Logo -->
<a href="/" class="text-xl font-bold">Logo</a>
<!-- Links (center) -->
<div class="hidden md:flex items-center gap-6">
<a href="#" class="text-gray-600 hover:text-gray-900">Home</a>
<a href="#" class="text-gray-600 hover:text-gray-900">About</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Contact</a>
</div>
<!-- Actions (right) -->
<div class="flex items-center gap-3">
<button class="px-4 py-2 text-sm">Enter</button>
<button class="px-4 py-2 bg-blue-600 text-white
rounded-lg text-sm">Register</button>
</div>
</nav>Navbar with flex: flex items-center justify-between distributes the logo, links and actions. h-16 for a consistent height. Links with hidden md:flex (hidden on mobile). gap-6 spaces the links. gap-3 for the action buttons. Universal navigation pattern — works in any project.
justify-content (main axis)
justify-start /* start (default) */ justify-end /* end */ justify-center /* center */ justify-between /* space BETWEEN items */ justify-around /* space AROUND each item */ justify-evenly /* EQUAL space between all */ justify-stretch /* stretch to fill */ /* Navbar: logo on the left, menu on the right */ <div class="flex justify-between items-center"> <span>Logo</span> <nav>Menu</nav> </div> /* Center horizontally: */ <div class="flex justify-center">...</div>
justify-* distributes items on the main axis (horizontal in flex-row, vertical in flex-col). justify-between = first item at the start, last at the end, space between the rest. justify-center = everything centered. The most used pattern in navbars: justify-between items-center (logo + menu at the extremes, vertically centered).
gap (space between items)
gap-0, gap-1, gap-2, gap-3, gap-4, gap-6, gap-8 gap-x-4 /* horizontal only (between columns) */ gap-y-2 /* vertical only (between rows) */ <div class="flex gap-4"> <div>Item</div> <div>Item</div> </div> <div class="grid grid-cols-3 gap-x-6 gap-y-4"> <div>Cell</div> </div> /* Responsive: */ class="gap-2 md:gap-6"
gap-* sets the spacing between flex/grid items — it replaces manual margins. gap-x-* = horizontal, gap-y-* = vertical. Cleaner than space-x-*/space-y-* (which use complex selectors). It works in flex AND grid. It accepts responsive prefixes: gap-2 md:gap-6 (more space on desktop). It's the modern way to space items.
order (visual order)
order-1, order-2, order-3 ... order-12 order-first /* order: -9999 */ order-last /* order: 9999 */ order-none /* order: 0 (default) */ <div class="flex"> <div class="order-2">Visually 2º</div> <div class="order-1">Visually 1º</div> </div> /* Responsive: image before text on desktop */ <div class="flex flex-col md:flex-row"> <div class="order-2 md:order-1">Text</div> <div class="order-1 md:order-2">Image</div> </div>
order-* visually reorders without changing the HTML (the DOM keeps the original order). order-first = first; order-last = last. Useful for responsive: order-1 md:order-2 changes the order on md+. The default is order-0. It doesn't affect accessibility (screen readers follow the DOM order, not the visual one).
Media object (image + text)
/* Comment / profile / notification */
<div class="flex gap-4">
<img class="shrink-0 size-12 rounded-full object-cover"
src="avatar.jpg" alt="Avatar">
<div class="min-w-0">
<p class="font-semibold truncate">John Silva</p>
<p class="text-sm text-gray-600">Comment here...</p>
<time class="text-xs text-gray-400">2 hours ago</time>
</div>
</div>
/* min-w-0 allows truncate on the text */The "average object" pattern (image + text alongside): flex gap-4 + image with shrink-0 (doesn't shrink) + content with min-w-0 (allows truncate). size-12 rounded-full for a circular avatar. min-w-0 is ESSENTIAL in flex to let the text truncate — without it, the content never shrinks below its natural size.
align-items (cross axis)
items-start /* top */ items-end /* bottom */ items-center /* vertical center */ items-baseline /* text baseline */ items-stretch /* stretch (default) */ /* Center vertically: */ <div class="flex items-center h-20"> <div>Centered vertically</div> </div> /* Baseline (labels + inputs aligned): */ <div class="flex items-baseline gap-2"> <label>Name:</label> <input class="text-lg"> </div>
items-* aligns items on the cross axis (vertical in flex-row). items-center = vertically centered (the most used). items-stretch (default) = all with the same height the the tallest. items-baseline = aligns by the text baseline (useful for labels + inputs of different sizes). items-start/items-end for top/bottom.
flex-grow and flex-shrink
grow /* flex-grow: 1 (grows to fill) */ grow-0 /* flex-grow: 0 (doesn't grow) */ shrink /* flex-shrink: 1 (shrinks if needed) */ shrink-0 /* flex-shrink: 0 (doesn't shrink) */ /* Input + button (classic pattern): */ <div class="flex gap-2"> <input class="grow" placeholder="Search..."> <button class="shrink-0 px-4">Fetch</button> </div> /* Fixed image + flexible text: */ <div class="flex gap-4"> <img class="shrink-0 size-16" src="avatar.jpg"> <p class="grow">Text that can be long...</p> </div>
grow = grows to fill available space. shrink-0 = doesn't shrink (buttons, labels, fixed-size images). Classic pattern: input with grow + button with shrink-0 (the input takes up all the space, the button keeps its size). Without shrink-0, the button may shrink and the text wrap.
Perfect centering
/* Flexbox (most common): */
<div class="flex items-center justify-center h-screen">
<div class="text-center">
Centered on both axes
</div>
</div>
/* Grid (one line): */
<div class="grid place-items-center h-screen">
<div>Centered</div>
</div>
/* Absolute + translate: */
<div class="relative h-64">
<div class="absolute top-1/2 left-1/2
-translate-x-1/2 -translate-y-1/2">
Centered
</div>
</div>Three ways to center: 1) flex items-center justify-center — the most used, needs a height on the parent (h-screen, h-full). 2) grid place-items-center — one line, more concise. 3) absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 — for overlays and modals. The modern pattern replaces the old margin: auto and transform hacks.
align-self (one item)
self-auto /* inherits from the parent (default) */ self-start /* top */ self-center /* center */ self-end /* bottom */ self-stretch /* stretch */ self-baseline /* baseline */ /* Button at the bottom of a card: */ <div class="flex flex-col h-full"> <h3>Title</h3> <p class="flex-1">Description...</p> <button class="self-end mt-auto">Buy</button> </div>
self-* overrides the parent's items-* on a single item. Same values: start, center, end, stretch, baseline. Useful when one item needs a different alignment from its siblings. Classic example: a button at the bottom of a card with self-end mt-auto (pushes it down regardless of the content above).
flex (shorthand)
flex-1 /* flex: 1 1 0% (grows equally) */ flex-auto /* flex: 1 1 auto (based on content) */ flex-initial /* flex: 0 1 auto (default) */ flex-none /* flex: none (doesn't grow/shrink) */ /* Equal columns: */ <div class="flex gap-4"> <div class="flex-1">A (1/3)</div> <div class="flex-1">B (1/3)</div> <div class="flex-1">C (1/3)</div> </div> /* Fixed sidebar + flexible content: */ <div class="flex"> <aside class="flex-none w-64">Sidebar</aside> <main class="flex-1">Content</main> </div>
flex-1 = all grow equally (columns of equal width). flex-auto = grows based on the content size. flex-none = fixed size (not flexible). flex-initial = default (shrinks but doesn't grow). Use flex-1 to split space into equal parts. flex-none + flex-1 = fixed sidebar + content that fills the rest.
space-between (legacy)
/* space-x/y adds margin between children (except the 1st) */ space-x-4 /* margin-left on children (except the 1st) */ space-y-2 /* margin-top on children (except the 1st) */ space-x-reverse /* TODAY: prefer gap */ <div class="flex gap-4"> ← modern, simple <div class="flex space-x-4"> ← legacy, complex selectors /* space-y is still useful for simple vertical stacks: */ <div class="space-y-4"> <p>Paragraph 1</p> <p>Paragraph 2</p> </div>
space-x-*/space-y-* add margin between children (except the first) using > * + * selectors. Today, gap-* is preferable: simpler, works in grid, no complex selectors. space-y-* is still convenient for simple vertical stacks (paragraphs, lists). In new code, always use gap.
Grid
Basic grid
<div class="grid grid-cols-3 gap-4"> <div class="bg-gray-100 p-4">1</div> <div class="bg-gray-100 p-4">2</div> <div class="bg-gray-100 p-4">3</div> <div class="bg-gray-100 p-4">4</div> </div> /* Responsive pattern (cards): */ class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" /* 1 col mobile → 2 on sm → 3 on lg → 4 on xl */
grid + grid-cols-* defines the number of columns. gap-4 spaces the cells. The responsive pattern for cards: grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 = 1 column on mobile, 2 on tablet, 3 on desktop, 4 on a large screen. Simpler than flex for regular grid layouts.
row-span (span rows)
row-span-1, row-span-2 ... row-span-6 row-span-full /* all rows */ <div class="grid grid-cols-3 grid-rows-3 gap-4"> <div class="row-span-2 bg-blue-100 p-4">Spans 2 rows</div> <div class="bg-gray-100 p-4">Normal</div> <div class="bg-gray-100 p-4">Normal</div> <div class="bg-gray-100 p-4">Normal</div> <div class="bg-gray-100 p-4">Normal</div> </div> /* Dashboard-style mosaic: */ <div class="col-span-2 row-span-2">Widget big</div>
row-span-* makes an item span multiple rows. row-span-2 = 2 rows tall. row-span-full = all rows. Useful for sidebars, large images in mosaics or dashboard widgets. Combine with col-span to create rectangular areas of any size in a grid.
Responsive grid (patterns)
/* Responsive cards: */ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> /* Sidebar + content: */ <div class="grid grid-cols-1 md:grid-cols-[250px_1fr] gap-8"> <aside>Sidebar</aside> <main>Content</main> </div> /* Auto-fit (no breakpoints): */ <div class="grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-6"> /* Holy grail (header + 3 cols + footer): */ <div class="grid grid-rows-[auto_1fr_auto] h-screen"> <header>Top</header> <div class="grid grid-cols-[200px_1fr_200px]">...</div> <footer>Footer</footer> </div>
Essential patterns: grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 for responsive cards. grid-cols-[250px_1fr] for a fixed sidebar + flexible content. repeat(auto-fit,minmax(280px,1fr)) = fully responsive grid with no media queries. "Holy grail" = nested grid with fixed header/footer and 3 columns. Grid is ideal for two-dimensional layouts.
grid-cols (number of columns)
grid-cols-1, grid-cols-2, grid-cols-3 ... grid-cols-12 grid-cols-none /* removes grid */ /* Arbitrary template: */ grid-cols-[200px_1fr] /* sidebar + content */ grid-cols-[1fr_2fr_1fr] /* 3 proportional columns */ grid-cols-[repeat(auto-fit,minmax(250px,1fr))] /* auto */ /* Responsive: */ class="grid-cols-2 md:grid-cols-3 lg:grid-cols-6" /* Underscores replace spaces in brackets */
grid-cols-1 to grid-cols-12 for equal columns. grid-cols-[200px_1fr] for a custom template (fixed sidebar + flexible content). repeat(auto-fit,minmax(250px,1fr)) = responsive grid WITHOUT breakpoints (automatic columns based on available space). Use underscores instead of spaces inside the brackets.
col-start and col-end
col-start-1, col-start-2 ... col-start-13 col-end-1, col-end-2 ... col-end-13 col-auto /* Item from column 2 to 4 (2 columns wide): */ <div class="col-start-2 col-end-4">Centered</div> /* Center content in a 12-column grid: */ <div class="grid grid-cols-12"> <div class="col-start-3 col-span-8">Content centered</div> </div> /* Asymmetric layout: */ <div class="col-start-1 col-span-7">Main</div> <div class="col-start-9 col-span-4">Lateral</div>
col-start-* and col-end-* position items on specific grid lines. The lines are numbered from 1 to N+1 (a 12-column grid has lines 1-13). col-start-2 col-end-4 = from line 2 to 4. Useful for asymmetric layouts, centering content (col-start-3 col-span-8 in a 12-column grid) and creating intentional empty spaces.
gap and spacing
gap-0, gap-1, gap-2, gap-3, gap-4, gap-6, gap-8 gap-x-4 /* only between columns (horizontal) */ gap-y-2 /* only between rows (vertical) */ <div class="grid grid-cols-3 gap-x-6 gap-y-4"> <div>A</div> <div>B</div> <div>C</div> </div> /* Responsive: */ class="gap-2 md:gap-6" /* No gap (edges touching): */ class="grid grid-cols-3 divide-x divide-y"
gap-* defines the space between grid cells. gap-x-* = horizontal (between columns), gap-y-* = vertical (between rows). It replaces the old negative-margin hack. It accepts responsive prefixes: gap-2 md:gap-6 (more space on desktop). For grids with no gap but with separators, use divide-x divide-y on the cells.
grid-rows
grid-rows-1, grid-rows-2 ... grid-rows-6 grid-rows-none /* Arbitrary template: */ grid-rows-[auto_1fr_auto] grid-rows-[200px_minmax(100px,1fr)] /* Full-height layout (header + main + footer): */ <div class="grid grid-rows-[auto_1fr_auto] h-screen"> <header class="bg-gray-100 p-4">Top</header> <main class="p-8 overflow-y-auto">Content (estica)</main> <footer class="bg-gray-100 p-4">Footer</footer> </div>
grid-rows-* defines the number of rows. grid-rows-[auto_1fr_auto] = classic layout: header with automatic height, main that stretches (1fr), automatic footer. 1fr = fraction of the available space. Combine with h-screen for a full-height layout with no scroll on the page (the scroll stays only in the main with overflow-y-auto).
place-items and place-content
/* Align items inside the cells: */ place-items-center /* center on both axes */ place-items-start place-items-end place-items-stretch /* default */ /* Align the whole grid in the container: */ place-content-center place-content-between place-content-around /* Perfect grid centering: */ <div class="grid place-items-center h-64"> <div>Centered na cell</div> </div> /* Icon centered in a square cell: */ <div class="grid place-items-center size-12 bg-gray-100 rounded-lg"> <svg class="size-6">...</svg> </div>
place-items-* aligns the content inside each cell (shortcut for items-* + justify-items-*). place-content-* aligns the whole grid in the container. place-items-center = perfect grid centering (one line). Useful for icons in square cells, avatars in table cells, and any simple centering.
col-span (span columns)
col-span-1, col-span-2 ... col-span-12 col-span-full /* spans ALL columns */ <div class="grid grid-cols-4 gap-4"> <div class="col-span-2 bg-blue-100 p-4">Spans 2 columns</div> <div class="bg-gray-100 p-4">1 col</div> <div class="bg-gray-100 p-4">1 col</div> <div class="col-span-full bg-green-100 p-4">Full row</div> </div> /* Highlighted item in a card grid: */ <div class="col-span-2 row-span-2">Highlight</div>
col-span-* makes an item span multiple columns. col-span-2 = 2 columns wide. col-span-full = all columns (full row — useful for headers, banners, separators). The item "pushes" the following ones to the available position. Combine with row-span for large rectangular areas (highlights in mosaics).
auto-flow (density)
grid-flow-row /* fills by rows (default) */ grid-flow-col /* fills by columns */ grid-flow-dense /* fills the holes */ grid-flow-row-dense /* rows + dense */ grid-flow-col-dense /* columns + dense */ /* Mosaic with no empty spaces: */ <div class="grid grid-cols-3 grid-flow-dense gap-4"> <div class="col-span-2">Wide</div> <div>Normal</div> <div>Normal</div> <div>Normal</div> <div>Normal</div> </div>
grid-flow-row (default) fills row by row. grid-flow-col fills column by column. grid-flow-dense fills the holes left by items with col-span (visually reorders to avoid empty spaces). Useful for Pinterest-style mosaics, image galleries and dashboards with widgets of varying sizes.
Forms and Inputs
Text input
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
Email
</label>
<input type="email"
class="w-full px-3 py-2 rounded-lg
border border-gray-300 shadow-sm
placeholder:text-gray-400
focus:outline-none focus:ring-2
focus:ring-blue-500 focus:border-blue-500
transition-colors"
placeholder="name@example.com">
</div>Complete input: w-full px-3 py-2 (size), rounded-lg border border-gray-300 (border), shadow-sm (subtle depth), focus:ring-2 focus:ring-blue-500 (accessible focus), placeholder:text-gray-400 (subtle placeholder). Label with text-sm font-medium text-gray-700. transition-colors smooths the state change.
Toggle switch
/* Toggle with peer (no JS): */
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="peer sr-only">
<div class="w-11 h-6 bg-gray-300 rounded-full
peer-checked:bg-blue-600
peer-focus:ring-2 peer-focus:ring-blue-300
after:content-[''] after:absolute after:top-0.5
after:left-0.5 after:bg-white after:rounded-full
after:h-5 after:w-5 after:transition-all
peer-checked:after:translate-x-5
transition-colors"></div>
</label>
/* peer-checked: changes the style when checked */Toggle switch without JavaScript: peer sr-only on the checkbox (hidden but accessible) + peer-checked:bg-blue-600 on the visual (changes color when checked). The "knob" uses after:content-[''] with after:translate-x-5 when checked to slide. sr-only keeps the input accessible to screen readers. transition-colors smooths it.
Disabled and readonly input
/* Disabled: */
<input disabled
class="w-full px-3 py-2 rounded-lg border border-gray-200
bg-gray-100 text-gray-500 cursor-not-allowed
placeholder:text-gray-400"
placeholder="Field desativado">
/* Readonly: */
<input readonly
class="w-full px-3 py-2 rounded-lg border border-gray-200
bg-gray-50 text-gray-600 cursor-default"
value="Not editable">
/* States via the disabled variant: */
class="disabled:opacity-50 disabled:cursor-not-allowed
disabled:bg-gray-100"Disabled: bg-gray-100 text-gray-500 cursor-not-allowed (disabled look). Readonly: bg-gray-50 text-gray-600 cursor-default (a slightly different look). Use the disabled: variant to style states: disabled:opacity-50 disabled:cursor-not-allowed. The difference: disabled doesn't submit the value; readonly submits it but can't be edited.
Visual validation (peer)
/* Input with validation via peer: */
<div>
<input type="email"
class="peer w-full px-3 py-2 rounded-lg border
border-gray-300
focus:ring-2 focus:ring-blue-500
invalid:border-red-500 invalid:ring-red-500"
required>
<p class="hidden peer-invalid:block mt-1
text-sm text-red-600">
Email invalid
</p>
<p class="hidden peer-valid:block mt-1
text-sm text-green-600">
✓ Email valid
</p>
</div>Visual validation with peer: invalid:border-red-500 on the input (red border when invalid). Messages: hidden peer-invalid:block (shows only when invalid) and hidden peer-valid:block (shows only when valid). It uses native HTML5 validation (required, type="email"). No JavaScript — the browser validates automatically.
Input with error
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
Password
</label>
<input type="password"
class="w-full px-3 py-2 rounded-lg
border border-red-500 ring-1 ring-red-500
focus:outline-none focus:ring-2 focus:ring-red-500
placeholder:text-gray-400">
<p class="mt-1 text-sm text-red-600">
A password must have at least 8 characters.
</p>
</div>
/* Success: border-green-500 + text-green-600 */Error state: border-red-500 ring-1 ring-red-500 (red border with a ring). Message: text-sm text-red-600 mt-1. For success: border-green-500 + a text-green-600 message. The ring-1 reinforces the visual cue. Always associate the message with the input via aria-describedby for accessibility.
Textarea
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
Message
</label>
<textarea rows="4"
class="w-full px-3 py-2 rounded-lg
border border-gray-300 shadow-sm
placeholder:text-gray-400
focus:outline-none focus:ring-2
focus:ring-blue-500 focus:border-blue-500
resize-y transition-colors"
placeholder="Write your message..."></textarea>
<p class="mt-1 text-xs text-gray-500">Max. 500 characters</p>
</div>Textarea: same classes the the input + rows="4" (initial height) + resize-y (allows vertical resize, not horizontal). resize-none to disable resizing completely. Help text: text-xs text-gray-500 mt-1. For larger textareas, use min-h-[120px]. The focus pattern is identical to the input: focus:ring-2 focus:ring-blue-500.
File input
/* Simple file input: */
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
File
</label>
<input type="file"
class="block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-lg file:border-0
file:text-sm file:font-medium
file:bg-blue-50 file:text-blue-700
hover:file:bg-blue-100
file:cursor-pointer cursor-pointer">
</div>
/* file: is the variant for the internal button */Styled file input: the file: variant styles the input's internal button. file:py-2 file:px-4 file:rounded-lg (button size), file:bg-blue-50 file:text-blue-700 (colors), hover:file:bg-blue-100 (hover). The input text: text-sm text-gray-500. file:border-0 removes the button's border. file:cursor-pointer for the cursor.
Select
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
Country
</label>
<select
class="w-full px-3 py-2 rounded-lg
border border-gray-300 shadow-sm
bg-white text-gray-900
focus:outline-none focus:ring-2
focus:ring-blue-500 focus:border-blue-500
appearance-none
bg-[url('data:image/svg+xml,...')] bg-no-repeat
bg-[right_0.75rem_center] bg-[length:1rem]">
<option>Portugal</option>
<option>Brasil</option>
</select>
</div>Styled select: same classes the the input + appearance-none (removes the browser's default arrow) + a custom icon via bg-[url(...)] positioned with bg-[right_0.75rem_center]. bg-white ensures a consistent background. For a simpler solution, use the @tailwindcss/forms plugin, which styles selects automatically with a consistent reset.
Input group (prefix/suffix)
/* With prefix: */
<div class="flex">
<span class="inline-flex items-center px-3
rounded-l-lg border border-r-0 border-gray-300
bg-gray-50 text-gray-500 text-sm">
https://
</span>
<input class="flex-1 px-3 py-2 rounded-r-lg
border border-gray-300 focus:ring-2
focus:ring-blue-500 focus:border-blue-500"
placeholder="example.com">
</div>
/* With an icon inside: */
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 size-5 text-gray-400">🔍</svg>
<input class="w-full pl-10 pr-4 py-2 rounded-lg border">
</div>Input group: prefix with rounded-l-lg border-r-0 bg-gray-50 + input with rounded-r-lg flex-1 (attached borders). For an icon inside the input: relative on the parent + absolute left-3 top-1/2 -translate-y-1/2 on the icon + pl-10 on the input (padding so it doesn't overlap the icon). A common pattern in search fields and URLs.
Plugin @tailwindcss/forms
/* Installation: */
npm install -D @tailwindcss/forms
/* v3 (config): */
plugins: [require('@tailwindcss/forms')]
/* v4 (CSS): */
@plugin "@tailwindcss/forms";
/* The plugin applies a consistent reset to: */
input, select, textarea, checkbox, radio
/* Before: inconsistent styles across browsers */
/* After: a clean, predictable base */
/* Normal usage with Tailwind classes: */
<input class="rounded-lg border-gray-300
focus:ring-blue-500 focus:border-blue-500">The @tailwindcss/forms plugin applies a consistent reset to all form elements (inputs, selects, textareas, checkboxes, radios). It removes inconsistent styles across browsers and provides a clean base. Then, style with normal Tailwind classes. Highly recommended in any project with forms. In v4, use @plugin in the CSS.
Checkbox and radio
/* Custom checkbox: */
<label class="flex items-center gap-3 cursor-pointer">
<input type="checkbox"
class="size-4 rounded border-gray-300
text-blue-600 focus:ring-blue-500
focus:ring-offset-0 cursor-pointer">
<span class="text-sm text-gray-700">I accept the terms</span>
</label>
/* Radio: */
<label class="flex items-center gap-3 cursor-pointer">
<input type="radio" name="option"
class="size-4 border-gray-300
text-blue-600 focus:ring-blue-500 cursor-pointer">
<span class="text-sm text-gray-700">Option A</span>
</label>Checkbox/radio with Tailwind: size-4 (size), rounded (checkbox) or no rounded (radio), border-gray-300 (border), text-blue-600 (color when checked), focus:ring-blue-500 (focus). cursor-pointer on the label to signal it's clickable. The @tailwindcss/forms plugin improves the reset. gap-3 spaces the input from the text.
Complete form
<form class="space-y-6 max-w-md">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Name</label>
<input class="w-full px-3 py-2 rounded-lg border border-gray-300
focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" class="w-full px-3 py-2 rounded-lg border
border-gray-300 focus:ring-2 focus:ring-blue-500">
</div>
<div class="flex items-center gap-2">
<input type="checkbox" class="size-4 rounded text-blue-600">
<label class="text-sm text-gray-600">Remember-me</label>
</div>
<button class="w-full py-2.5 bg-blue-600 text-white rounded-lg
font-medium hover:bg-blue-700 transition-colors">
Enter
</button>
</form>Complete form: space-y-6 spaces the fields. Each field: label (text-sm font-medium) + input (w-full px-3 py-2 rounded-lg border). Checkbox with flex items-center gap-2. Full-width button: w-full py-2.5 bg-blue-600 rounded-lg font-medium. max-w-md limits the width for readability. A universal form pattern.
Floating label
/* Label that rises on focus (with peer): */
<div class="relative">
<input type="text" id="name" placeholder=" "
class="peer w-full px-3 pt-5 pb-2 rounded-lg
border border-gray-300
focus:ring-2 focus:ring-blue-500">
<label for="name"
class="absolute left-3 top-3.5 text-gray-500
transition-all duration-200
peer-focus:top-1.5 peer-focus:text-xs
peer-focus:text-blue-600
peer-[:not(:placeholder-shown)]:top-1.5
peer-[:not(:placeholder-shown)]:text-xs">
Name
</label>
</div>Floating label: peer on the input + peer-focus:top-1.5 peer-focus:text-xs on the label (rises and shrinks on focus). peer-[:not(:placeholder-shown)]: keeps the label up when there's content. placeholder=" " (a space) is needed for the selector to work. pt-5 pb-2 gives room for the label. transition-all smooths the animation.
Practical Components
Reusable button
<button class="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-blue-600 text-white font-medium hover:bg-blue-700 active:bg-blue-800 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none transition-colors duration-200"> <svg class="size-5">...</svg> Click here </button> /* Variants: outline, ghost, danger */ class="border-2 border-blue-600 text-blue-600 hover:bg-blue-50" /* outline */
Complete button: inline-flex items-center gap-2 (icon + text aligned), px-5 py-2.5 (padding), rounded-lg (corners), hover/active/focus/disabled states, transition-colors (smooth). focus:ring-offset-2 for visible accessibility. Variants: outline (border-2), ghost (hover:bg-gray-100), danger (bg-red-600).
Skeleton loading
<div class="animate-pulse space-y-4">
<div class="h-48 bg-gray-200 rounded-lg"></div>
<div class="space-y-2">
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
<div class="h-4 bg-gray-200 rounded w-1/2"></div>
<div class="h-4 bg-gray-200 rounded w-5/6"></div>
</div>
<div class="flex gap-3">
<div class="size-10 bg-gray-200 rounded-full"></div>
<div class="h-4 bg-gray-200 rounded w-24 self-center"></div>
</div>
</div>Skeleton: animate-pulse + bg-gray-200 rounded blocks with varied heights/widths. h-4 w-3/4 simulates text lines. size-10 rounded-full simulates an avatar. space-y-* spaces the blocks. Replace with the real content once loaded (via JS/framework). An essential loading-UX pattern — it shows the structure while the data isn't there yet.
Alert / Toast
/* Alert (inline): */ <div class="flex items-center gap-3 p-4 rounded-lg bg-green-50 border border-green-200"> <svg class="size-5 text-green-600 shrink-0">✓</svg> <p class="text-sm text-green-800">Operation realizada!</p> </div> /* Toast (fixed in the corner): */ <div class="fixed bottom-4 right-4 z-50 flex items-center gap-3 px-4 py-3 bg-gray-900 text-white rounded-lg shadow-xl animate-in slide-in-from-bottom"> <span>Message guardada</span> <button class="text-gray-400 hover:text-white">×</button> </div>
Inline alert: flex items-center gap-3 p-4 rounded-lg + semantic colors (bg-green-50 border-green-200). Icon with shrink-0 (doesn't shrink). Toast: fixed bottom-4 right-4 z-50 (bottom-right corner), bg-gray-900 text-white shadow-xl (dark, prominent). Close button: text-gray-400 hover:text-white. Use animate-in for a smooth entrance.
Avatar group
/* Group of overlapping avatars: */
<div class="flex -space-x-3">
<img class="size-10 rounded-full ring-2 ring-white
object-cover" src="a1.jpg">
<img class="size-10 rounded-full ring-2 ring-white
object-cover" src="a2.jpg">
<img class="size-10 rounded-full ring-2 ring-white
object-cover" src="a3.jpg">
<span class="size-10 rounded-full ring-2 ring-white
bg-gray-200 flex items-center justify-center
text-xs font-medium text-gray-600">
+5
</span>
</div>
/* -space-x-3 overlaps; ring-2 ring-white separates */Avatar group: flex -space-x-3 overlaps the avatars (negative margin). ring-2 ring-white creates white separation between them. size-10 rounded-full object-cover for uniform circular avatars. Counter: bg-gray-200 flex items-center justify-center text-xs (gray circle with "+5"). Popular in member lists, comments and team activity.
Card with hover
<div class="group max-w-sm rounded-xl overflow-hidden
bg-white shadow-md hover:shadow-xl
transition-shadow duration-300">
<div class="overflow-hidden">
<img src="photo.jpg" alt=""
class="w-full h-48 object-cover
group-hover:scale-105 transition-transform duration-300">
</div>
<div class="p-6">
<h3 class="font-semibold text-lg
group-hover:text-blue-600 transition-colors">
Title
</h3>
<p class="mt-2 text-gray-600 text-sm">Description...</p>
</div>
</div>Card with effects: hover:shadow-xl (elevation), group-hover:scale-105 (image zoom), group-hover:text-blue-600 (color on the title). overflow-hidden on the image container + rounded-xl for rounded corners. transition-* smooths everything. group propagates the card's hover to the children. An essential pattern for product lists, blog posts and portfolios.
Modal / Dialog
<div class="fixed inset-0 z-50 flex items-center
justify-center p-4">
<!-- Overlay -->
<div class="absolute inset-0 bg-black/50
backdrop-blur-sm"></div>
<!-- Dialog -->
<div class="relative bg-white rounded-2xl shadow-xl
w-full max-w-md p-6 z-10">
<h2 class="text-lg font-semibold">Title</h2>
<p class="mt-2 text-gray-600">Modal content...</p>
<div class="mt-6 flex justify-end gap-3">
<button class="px-4 py-2 text-gray-700
hover:bg-gray-100 rounded-lg">Cancel</button>
<button class="px-4 py-2 bg-blue-600 text-white
rounded-lg hover:bg-blue-700">Confirm</button>
</div>
</div>
</div>Modal: fixed inset-0 z-50 (full-screen overlay), bg-black/50 backdrop-blur-sm (dark frosted background), flex items-center justify-center (centers the dialog). Dialog: relative bg-white rounded-2xl shadow-xl max-w-md. z-10 above the overlay. Buttons: flex justify-end gap-3. Use the native <dialog> or Headless UI for full accessibility.
Responsive table
<div class="overflow-x-auto rounded-lg border border-gray-200">
<table class="w-full text-sm text-left">
<thead class="bg-gray-50 text-gray-600 uppercase text-xs">
<tr>
<th class="px-4 py-3 font-medium">Name</th>
<th class="px-4 py-3 font-medium">Email</th>
<th class="px-4 py-3 font-medium">State</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr class="hover:bg-gray-50 transition-colors">
<td class="px-4 py-3 font-medium">John</td>
<td class="px-4 py-3 text-gray-600">joao@mail.com</td>
<td class="px-4 py-3">
<span class="px-2 py-1 bg-green-100 text-green-800
rounded-full text-xs">Active</span>
</td>
</tr>
</tbody>
</table>
</div>Table: overflow-x-auto on the parent (horizontal scroll on mobile). w-full text-sm text-left on the table. Header: bg-gray-50 uppercase text-xs. Rows: divide-y divide-gray-200 (separators). Hover: hover:bg-gray-50. Status badge with semantic colors. px-4 py-3 for consistent padding. Essential for dashboards and listings.
Breadcrumb
<nav class="flex items-center gap-2 text-sm text-gray-500">
<a href="/" class="hover:text-gray-900 transition-colors">Home</a>
<span class="text-gray-300">/</span>
<a href="/products" class="hover:text-gray-900">Products</a>
<span class="text-gray-300">/</span>
<span class="text-gray-900 font-medium" aria-current="page">
Detail
</span>
</nav>
/* With arrow icon: */
<svg class="size-4 text-gray-400">›</svg>
/* Separator with slash, arrow or icon */Breadcrumb: flex items-center gap-2 text-sm (horizontal line). Links: text-gray-500 hover:text-gray-900 (gray, darkens on hover). Current page: text-gray-900 font-medium + aria-current="page" (accessibility). Separator: text-gray-300 (slash, arrow or icon). Essential for hierarchical navigation (e-commerce, documentation, dashboards).
Responsive navbar
<nav class="sticky top-0 z-50 bg-white/80
backdrop-blur-md border-b border-gray-200">
<div class="container mx-auto px-4 h-16
flex items-center justify-between">
<a href="/" class="text-xl font-bold">Logo</a>
<div class="hidden md:flex items-center gap-6">
<a href="#" class="text-gray-600 hover:text-gray-900
transition-colors">Home</a>
<a href="#" class="text-gray-600 hover:text-gray-900">About</a>
<button class="px-4 py-2 bg-blue-600 text-white
rounded-lg text-sm font-medium hover:bg-blue-700">
Enter
</button>
</div>
<button class="md:hidden text-2xl">☰</button>
</div>
</nav>Modern navbar: sticky top-0 z-50 (fixed on scroll), bg-white/80 backdrop-blur-md (frosted glass), flex items-center justify-between (logo + menu at the edges). Links hidden md:flex (desktop), button md:hidden (mobile). h-16 for a consistent height. container mx-auto px-4 limits the width. A universal navigation pattern.
Dropdown menu
<div class="relative inline-block">
<button class="px-4 py-2 border rounded-lg">Options ▼</button>
<!-- Menu (hidden by default, show via JS) -->
<div class="absolute right-0 mt-2 w-48
bg-white rounded-lg shadow-lg border border-gray-200
py-1 z-10">
<a href="#" class="block px-4 py-2 text-sm text-gray-700
hover:bg-gray-100 transition-colors">Profile</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700
hover:bg-gray-100">Settings</a>
<hr class="my-1 border-gray-200">
<a href="#" class="block px-4 py-2 text-sm text-red-600
hover:bg-red-50">Exit</a>
</div>
</div>Dropdown: relative on the parent + absolute right-0 mt-2 on the menu (positioned below, right-aligned). w-48 bg-white rounded-lg shadow-lg border (visual). Items: block px-4 py-2 text-sm hover:bg-gray-100. Separator: hr border-gray-200. Dangerous item: text-red-600 hover:bg-red-50. z-10 above the content. Show/hide via JS or data-[state=open]:block.
Hero section
<section class="relative bg-gradient-to-br from-blue-600
to-purple-700 text-white py-20 md:py-32">
<div class="container mx-auto px-4 text-center">
<h1 class="text-4xl md:text-6xl font-bold
leading-tight max-w-3xl mx-auto">
Build something amazing
</h1>
<p class="mt-6 text-lg md:text-xl text-blue-100
max-w-2xl mx-auto">
Description of the product or service in a few words.
</p>
<div class="mt-10 flex flex-col sm:flex-row
gap-4 justify-center">
<a href="#" class="px-8 py-3 bg-white text-blue-700
rounded-lg font-semibold hover:bg-blue-50">Get started</a>
<a href="#" class="px-8 py-3 border-2 border-white/50
rounded-lg font-semibold hover:bg-white/10">Learn more</a>
</div>
</div>
</section>Hero section: bg-gradient-to-br from-blue-600 to-purple-700 (gradient background), py-20 md:py-32 (generous vertical space). Title: text-4xl md:text-6xl font-bold max-w-3xl mx-auto (large, centered, constrained). Buttons: flex flex-col sm:flex-row gap-4 justify-center (stacks on mobile). Primary CTA: white background; secondary: border-2 border-white/50.
Tabs
<div class="border-b border-gray-200">
<nav class="flex gap-6 -mb-px">
<button class="pb-3 px-1 border-b-2 border-blue-600
text-blue-600 font-medium text-sm">
Active
</button>
<button class="pb-3 px-1 border-b-2 border-transparent
text-gray-500 hover:text-gray-700
hover:border-gray-300 text-sm transition-colors">
Inactive
</button>
</nav>
</div>
/* Content below: */
<div class="py-4">Content do tab active</div>Tabs: border-b border-gray-200 on the container (baseline). Active tab: border-b-2 border-blue-600 text-blue-600 font-medium (blue underline). Inactive: border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300. -mb-px overlaps the baseline. pb-3 px-1 for padding. Show/hide content via JS or hidden.
Badge and tag
/* Simple badge: */ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800"> Active </span> /* With status dot: */ <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm bg-blue-50 text-blue-700"> <span class="size-2 rounded-full bg-blue-500"></span> Online </span> /* Removable (with hover): */ <span class="group inline-flex items-center gap-1 px-3 py-1 rounded-full bg-gray-100 text-sm"> Tag <button class="group-hover:text-red-600">×</button> </span>
Badge: inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium + semantic colors (bg-green-100 text-green-800). Status dot: size-2 rounded-full bg-blue-500. Subtle colors (background 50/100, text 700/800) for legible contrast without being aggressive. Removable: group + group-hover:text-red-600 on the × button.
Tooltip
/* Tooltip with group (CSS only): */
<div class="relative group inline-block">
<button class="px-4 py-2 bg-gray-100 rounded-lg">
Hover here
</button>
<div class="absolute bottom-full left-1/2 -translate-x-1/2
mb-2 px-3 py-1.5 bg-gray-900 text-white text-xs
rounded-lg opacity-0 group-hover:opacity-100
transition-opacity pointer-events-none
whitespace-nowrap">
Text do tooltip
<div class="absolute top-full left-1/2 -translate-x-1/2
border-4 border-transparent border-t-gray-900"></div>
</div>
</div>CSS-only tooltip: group on the parent + opacity-0 group-hover:opacity-100 on the tooltip (appears on hover). Position: absolute bottom-full left-1/2 -translate-x-1/2 mb-2 (above, centered). pointer-events-none so it doesn't interfere with the mouse. Arrow: border-4 border-transparent border-t-gray-900. whitespace-nowrap prevents wrapping. For production, use Headless UI or Radix.
Footer
<footer class="bg-gray-900 text-gray-400 py-12">
<div class="container mx-auto px-4
grid grid-cols-1 md:grid-cols-4 gap-8">
<div>
<h3 class="text-white font-semibold mb-4">Logo</h3>
<p class="text-sm">Description da company...</p>
</div>
<div>
<h4 class="text-white font-medium mb-3 text-sm
uppercase tracking-wide">Product</h4>
<ul class="space-y-2 text-sm">
<li><a href="#" class="hover:text-white transition-colors">Features</a></li>
<li><a href="#" class="hover:text-white">Prices</a></li>
</ul>
</div>
</div>
<div class="container mx-auto px-4 mt-8 pt-8
border-t border-gray-800 text-sm text-center">
© 2024 Company
</div>
</footer>Footer: bg-gray-900 text-gray-400 py-12 (dark background). Column grid: grid-cols-1 md:grid-cols-4 gap-8. Titles: text-white font-medium uppercase tracking-wide text-sm. Links: hover:text-white transition-colors. Separator: border-t border-gray-800 mt-8 pt-8. Centered copyright. A universal footer pattern with multiple columns of links.
Progress bar
/* Progress bar: */
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-blue-600 h-2.5 rounded-full transition-all
duration-500" style="width: 65%"></div>
</div>
/* With label: */
<div class="flex justify-between text-sm mb-1">
<span class="font-medium text-gray-700">Progresso</span>
<span class="text-gray-500">65%</span>
</div>
/* Step progress: */
<div class="flex items-center gap-2">
<div class="size-8 rounded-full bg-blue-600 text-white
flex items-center justify-center text-sm">1</div>
<div class="flex-1 h-1 bg-blue-600"></div>
<div class="size-8 rounded-full bg-gray-200 text-gray-500
flex items-center justify-center text-sm">2</div>
</div>Progress bar: w-full bg-gray-200 rounded-full h-2.5 (track) + bg-blue-600 h-2.5 rounded-full (fill with style="width: 65%"). transition-all duration-500 animates the change. Label: flex justify-between text-sm. Step progress: numbered circles (size-8 rounded-full) + connector lines (flex-1 h-1). Essential for wizards and uploads.