Tailwind CSS Guidelines
Overview
Foundational Tailwind CSS guidelines for utility-first styling. Covers class composition, responsive design, dark mode, color systems, typography, spacing, state variants, and Tailwind v4 changes. Prioritize utility classes over custom CSS, compose with clsx /tw-merge , and follow mobile-first responsive patterns.
Utility-First Methodology
Rule Detail
Default to utilities Write className="mt-4 text-sm font-medium" before reaching for custom CSS
Custom CSS only for complex selectors Animations, ::before /::after , or styles utilities cannot express
Never use @apply in components Defeats utility-first purpose; use clsx /tw-merge composition instead
@apply only in base layer Acceptable for resetting third-party styles or global typography
One semantic class per component Extract via component abstraction, not CSS abstraction
Class Composition
Tool Purpose When to Use
clsx
Conditional class joining Toggling classes based on props/state
tw-merge
Tailwind-aware merge with conflict resolution Overriding default styles in component variants
clsx
- tw-merge
Combined via cn() helper Standard pattern for component libraries
import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge";
function cn(...inputs: ClassValue[]): string { return twMerge(clsx(inputs)); }
// Usage: last class wins for conflicts cn("px-4 py-2 bg-blue-500", isActive && "bg-blue-700", className)
Pattern Correct Incorrect
Conditional classes cn("text-sm", isLarge && "text-lg")
String interpolation: text-${size}
Dynamic values Use style prop or CSS variables text-[${dynamicValue}px]
Component variants cn(baseStyles, variants[variant])
Deeply nested ternaries
Override defaults cn("px-4", className) via tw-merge Manual class deduplication
Responsive Design
Mobile-first: base styles apply to all screens, breakpoint prefixes apply at that width and above.
Breakpoint Min Width Typical Target
(none) 0px Mobile (default)
sm:
640px Large phones, small tablets
md:
768px Tablets
lg:
1024px Laptops
xl:
1280px Desktops
2xl:
1536px Large screens
Common Responsive Patterns
Pattern Classes Effect
Stack to row flex flex-col md:flex-row
Column on mobile, row on desktop
Column count grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3
Responsive grid columns
Hide on mobile hidden md:block
Visible only at md and above
Show on mobile only block md:hidden
Visible only below md
Responsive text text-sm md:text-base lg:text-lg
Scale text with screen
Responsive padding px-4 md:px-6 lg:px-8
Increase padding with screen
Full to constrained w-full max-w-md mx-auto
Full mobile, centered container on desktop
Responsive gap gap-4 md:gap-6 lg:gap-8
Increase spacing with screen
Container Queries
Rule Detail
Use @container for component-level responsiveness When component lives in varying-width contexts
Apply @container on parent Parent gets the container class
Query with @sm: , @md: , etc. Breakpoints relative to container, not viewport
<div class="@container"> <div class="flex flex-col @md:flex-row">...</div> </div>
Dark Mode
Rule Detail
Use dark: variant for every color Pair every light color with its dark counterpart
v4: Use @custom-variant
@custom-variant dark (&:where(.dark, .dark *)) for class-based toggle
v3: Set strategy in config darkMode: 'class' (toggle) or 'media' (system preference)
Test both modes Every component must be visually verified in both modes
Use semantic tokens Prefer CSS variables over hardcoded dark/light pairs for theming
Color Pairing Strategy
Element Light Dark
Page background bg-white
dark:bg-gray-950
Card/surface bg-white or bg-gray-50
dark:bg-gray-900
Primary text text-gray-900
dark:text-white
Secondary text text-gray-600
dark:text-gray-400
Muted text text-gray-500
dark:text-gray-500
Borders border-gray-200
dark:border-gray-700
Subtle borders border-gray-900/10
dark:border-white/10
Input background bg-white
dark:bg-white/5
Hover surface hover:bg-gray-50
dark:hover:bg-gray-800
Opacity-Based Dark Mode Colors
Pattern Use Case
bg-white/5
Subtle elevated surface in dark mode
bg-white/10
Hover state overlay in dark mode
border-white/10
Subtle border in dark mode
ring-white/15
Decorative ring in dark mode
text-white/70
De-emphasized text in dark mode
Accessibility
Rule Detail
forced-colors: variant Override styles when user has forced-colors enabled
Maintain 4.5:1 contrast ratio For body text in both modes
Maintain 3:1 contrast ratio For large text and UI components
Test with prefers-contrast: more
Ensure readability in high-contrast mode
Color System
Color Hierarchy
Role Default Shade Usage
Primary action indigo-600 / dark:indigo-500
Buttons, links, active states
Primary hover indigo-500 / dark:indigo-400
Hover on primary actions
Heading text gray-900 / dark:white
Page titles, section headings
Body text gray-600 / dark:gray-300
Paragraphs, descriptions
Muted/caption gray-500 / dark:gray-400
Timestamps, helper text
Placeholder gray-400 / dark:gray-500
Input placeholders
Success green-600 / dark:green-400
Confirmation, valid states
Warning yellow-600 / dark:yellow-500
Caution, attention
Danger red-600 / dark:red-500
Errors, destructive actions
Opacity Modifiers
Syntax Effect
bg-red-500/10
Red background at 10% opacity (subtle tint)
bg-indigo-600/90
Slightly transparent primary
text-gray-900/80
Slightly muted text
border-gray-900/10
Very subtle border
CSS Variable Theming
<!-- Inline theme overrides --> <button class="bg-[var(--btn-bg)] text-[var(--btn-text)]" style="--btn-bg: var(--color-indigo-600); --btn-text: white;"> Submit </button>
<!-- Tailwind v4: @theme for custom tokens -->
Theming rule Detail
Use CSS variables for brand colors Allows runtime theme switching
Keep utility classes for standard colors Use variables only when dynamic theming is needed
Arbitrary value syntax for variables bg-[var(--color-brand)] or bg-(--color-brand) in v4
Typography
Text Size Scale
Class Size Default Line Height Common Pairing
text-xs
0.75rem 1rem Captions, badges
text-sm
0.875rem 1.25rem Secondary text, labels
text-base
1rem 1.5rem Body text
text-lg
1.125rem 1.75rem Subheadings
text-xl
1.25rem 1.75rem Section headings
text-2xl
1.5rem 2rem Page titles
text-3xl
1.875rem 2.25rem Hero subheadings
text-4xl
2.25rem 2.5rem Hero headings
Line Height Slash Syntax (v4)
Syntax Effect
text-sm/6
text-sm with line-height: 1.5rem
text-base/7
text-base with line-height: 1.75rem
text-xl/8
text-xl with line-height: 2rem
text-sm/[17px]
Custom arbitrary line height
Font Weight Pairing
Element Weight Class
Headings Semibold or bold font-semibold or font-bold
Body Normal font-normal (default)
Labels Medium font-medium
Captions Normal or medium font-normal or font-medium
Emphasis Semibold font-semibold
Text Wrapping
Class Use Case
text-pretty
Avoids orphan words at end of paragraphs (preferred for body text)
text-balance
Evenly balanced line lengths (preferred for headings)
truncate
Single line with ellipsis overflow
line-clamp-3
Multi-line truncation (3 lines)
Responsive Text
<h1 class="text-2xl font-bold tracking-tight sm:text-3xl lg:text-4xl"> Page Title </h1> <p class="text-sm/6 text-gray-600 md:text-base/7"> Description text that scales with viewport. </p>
Spacing and Sizing
Spacing Scale Consistency
Spacing Value Common Use
1
0.25rem (4px) Tight icon gaps
1.5
0.375rem (6px) Small vertical padding
2
0.5rem (8px) Compact element padding
3
0.75rem (12px) Standard small padding
4
1rem (16px) Standard padding and gap
6
1.5rem (24px) Section spacing
8
2rem (32px) Large section spacing
10
2.5rem (40px) Content block separation
12
3rem (48px) Major section separation
16
4rem (64px) Page-level spacing
Gap vs Space
Utility Use When
gap-*
Flex/grid layouts (preferred, handles wrapping correctly)
space-y-*
Vertical stack of direct children, no flex/grid
space-x-*
Horizontal stack of direct children, no flex/grid
Prefer gap over space-* in flex/grid contexts. space-* uses margins and breaks with flex-wrap .
Common Padding Pairs
Context Horizontal Vertical Combined
Small button px-2.5
py-1.5
px-2.5 py-1.5
Standard button px-3
py-2
px-3 py-2
Large button px-4
py-2.5
px-4 py-2.5
Card px-4 or px-6
py-4 or py-5
p-4 or px-6 py-5
Input field px-3
py-1.5 or py-2
px-3 py-1.5
Section px-4 sm:px-6 lg:px-8
py-12 sm:py-16
Responsive both axes
Negative Margins
Pattern Use Case
-mx-4
Bleed content to edge of padded parent
-mt-1
Pull element up for optical alignment
-ml-px
Overlap 1px borders between siblings
Borders, Rings, and Outlines
When to Use Each
Utility Purpose Typical Use
border
Structural boundary Card edges, input fields, dividers
ring
Decorative/state highlight Focus states, selection indicator
outline
Accessibility focus indicator Keyboard focus visibility
Focus Patterns
Pattern Classes Use Case
Standard focus ring focus:outline-2 focus:outline-offset-2 focus:outline-indigo-600
Buttons, links
Input focus focus:ring-2 focus:ring-inset focus:ring-indigo-600 or focus:outline-2 focus:outline-indigo-600
Form fields
Focus-visible only focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600
Interactive elements (keyboard only)
No focus ring Never remove without replacement Accessibility violation
Subtle Borders with Opacity
Pattern Classes
Subtle card border border border-gray-900/10 dark:border-white/10
Separator border-t border-gray-900/5
Elevated surface ring-1 ring-gray-900/5 dark:ring-white/5
Inset Rings
Pattern Classes Use Case
Inset input ring ring-1 ring-inset ring-gray-300
Default input border
Inset focus ring ring-2 ring-inset ring-indigo-600
Focused input
Inset error ring ring-2 ring-inset ring-red-500
Validation error
State Variants
Standard States
Variant Applies When Common Use
hover:
Mouse over Background/text color change
focus:
Element focused (any method) Outline/ring for inputs
focus-visible:
Keyboard focus only Outline for buttons/links
active:
Being clicked/pressed Scale down, darken
disabled:
Element disabled Opacity reduction, cursor change
visited:
Link visited Subtle color change
Disabled Pattern
<button class="bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-indigo-600"> Submit </button>
Group and Peer States
Pattern Trigger Classes
Group hover Parent hovered Parent: group , child: group-hover:text-white
Group focus Parent focused Parent: group , child: group-focus:ring-2
Group data state Parent has data attr Parent: group , child: group-data-[open]:rotate-180
Named groups Multiple nested groups Parent: group/item , child: group-hover/item:text-white
Peer checked Sibling checked Sibling: peer , target: peer-checked:bg-indigo-600
Peer focus Sibling focused Sibling: peer , target: peer-focus:ring-2
Peer invalid Sibling invalid Sibling: peer , target: peer-invalid:text-red-500
Data Attribute Styling
Pattern Classes
Toggle checked data-[checked]:bg-indigo-600
Open state data-[open]:bg-gray-50
Active tab data-[selected]:border-indigo-500 data-[selected]:text-indigo-600
State text color data-[disabled]:text-gray-400
Headless UI integration data-[open]:rotate-180 on disclosure chevron
Tailwind v4 Changes
Change v3 v4
Configuration tailwind.config.js
CSS-first: @theme in CSS file
Custom utilities Plugin API in config @utility directive in CSS
Color opacity bg-red-500/50
Same syntax, now also bg-red-500/[0.5]
Line height text-sm leading-6
text-sm/6 (slash syntax)
CSS variables theme() function var(--color-*) native access
Import @tailwind base/components/utilities
@import "tailwindcss"
Arbitrary properties [--my-var:value]
Same, plus (--my-var) shorthand
@theme Directive
@import "tailwindcss";
@theme { --color-brand: #4f46e5; --color-brand-light: #818cf8; --font-display: "Inter", sans-serif; --breakpoint-3xl: 1920px; }
@utility Directive
@utility scrollbar-hidden { scrollbar-width: none; &::-webkit-scrollbar { display: none; } }
/* Usage: class="scrollbar-hidden" */
@custom-variant Directive
/* Class-based dark mode toggle */ @custom-variant dark (&:where(.dark, .dark *));
/* Custom theme variants */ @custom-variant theme-midnight (&:where([data-theme="midnight"] *));
@variant Directive (Apply Variants in Custom CSS)
.my-element { background: white; @variant dark { background: black; } }
@source Directive (Content Detection)
@import "tailwindcss"; /* Include external library templates / @source "../node_modules/@my-company/ui-lib"; / Exclude legacy code / @source not "./src/legacy"; / Safelist specific utilities */ @source inline("underline"); @source inline("{hover:,}bg-red-{50,{100..900..100},950}");
Built-in Container Queries (No Plugin)
<div class="@container"> <div class="grid grid-cols-1 @sm:grid-cols-3 @lg:grid-cols-4">...</div> </div> <!-- Named containers, max-width, ranges --> <div class="@container/main"> <div class="@sm:@max-md:grid-cols-2">...</div> </div>
v4 New Features
Feature Syntax Use Case
Dynamic spacing w-17 , pr-29 , grid-cols-15
Any numeric value without config
Gradient rename bg-linear-to-r (was bg-gradient-to-r ) Linear gradients
Gradient angles bg-linear-45
Arbitrary gradient angles
Radial/conic bg-radial-* , bg-conic-*
Radial and conic gradients
OKLCH colors Default palette Wider gamut, more vivid
3D transforms rotate-x-* , rotate-y-* , perspective-*
3D effects
Not variant not-hover:opacity-100
Negation
In variant in-data-open:block
Implicit group (ancestor match)
Nth variant nth-3:bg-gray-50
Child targeting
Starting style starting:opacity-0
Entry animations (@starting-style )
Inert variant inert:opacity-50
Non-interactive elements
v4.1 Features
Feature Syntax Use Case
Text shadows text-shadow-sm , text-shadow-lg
Text readability on images
Text shadow color text-shadow-sky-300/50
Colored text glow
Masks mask-b-from-50% , mask-radial-*
Image fade/reveal effects
Colored drop shadows drop-shadow-cyan-500/50
Colored SVG/element shadows
Overflow wrap wrap-break-word , wrap-anywhere
Long word handling
Input device pointer-fine: , pointer-coarse:
Mouse vs touch adaptation
Safe alignment justify-center-safe , items-center-safe
Auto-fix overflow alignment
User validation user-valid: , user-invalid:
Post-interaction form states
No-JS fallback noscript:block
Styles when JS disabled
v4 Migration Checklist
Action Detail
Replace config file Move theme tokens to @theme in CSS
Update imports @import "tailwindcss" replaces three @tailwind directives
Test slash syntax text-sm/6 replaces text-sm leading-6
Audit plugins Move custom utilities to @utility , variants to @custom-variant
Check color references theme() calls become var(--color-*)
Rename gradients bg-gradient-to-r → bg-linear-to-r
Content detection Remove content array, use @source for non-auto paths
Container queries Remove @tailwindcss/container-queries plugin, use built-in @container
Run upgrade tool npx @tailwindcss/upgrade for automated migration
Common Anti-Patterns
Anti-Pattern Why It Is Wrong Correct Approach
@apply in component files Defeats utility-first; hides styles from scanning Use cn() or direct utility classes
Dynamic class construction: text-${color}-500
Tailwind cannot scan dynamic strings; classes get purged Use complete class names: cn(colorMap[color])
Inline style for Tailwind-expressible values Bypasses design system, no responsive/state variants Use utility classes or arbitrary values w-[72px]
Nesting Tailwind in CSS files Creates specificity issues and maintenance burden Use utilities in markup, CSS only for base styles
Hardcoded colors without dark mode Broken in dark mode, inconsistent theming Always pair: bg-white dark:bg-gray-950
Using !important via ! prefix Specificity hack that masks structural issues Fix class order or use tw-merge for overrides
Redundant classes: flex flex-row
flex-row is the default flex direction Just use flex
Missing focus-visible on interactive elements Keyboard users cannot see focus indicator Always add focus-visible styles
Using space-* with flex-wrap
Margins from space-* break wrapped layouts Use gap-* instead
Deeply nested arbitrary values: [calc(100%-var(--x))]
Unreadable, hard to maintain Extract to CSS variable or custom utility
Using pixel values everywhere: w-[347px]
Breaks responsiveness and spacing consistency Use Tailwind scale values: w-80 , max-w-sm
Not using tw-merge for component overrides Later classes do not reliably override earlier ones Wrap with cn() using tw-merge
Applying text-* color to SVG text-* sets CSS color , SVGs need fill /stroke
Use fill-current or stroke-current with text-*
Forgetting responsive prefixes on layout Layout breaks on different screen sizes Always test and apply sm: , md: , lg: as needed
Using sm: to target mobile sm: means 640px+, not "small screens" Unprefixed = mobile; sm: = tablet+
Stacking conflicting utilities: grid flex
Later in stylesheet wins, not later in class string Use conditional: isGrid ? "grid" : "flex"
Arbitrary values when token exists Bypasses design system consistency Use w-80 not w-[320px] when token matches
MCP Component Library
The frontend-components MCP server provides browsable component libraries for reference and reuse:
Framework ID What it provides
HyperUI hyperui
481 pure HTML/Tailwind components (application, marketing, neobrutalism)
DaisyUI daisyui
65 component class references with examples
FlyonUI flyonui
49 CSS components + 24 JS plugins
HeadlessUI React headlessui-react
38 accessible React component examples
HeadlessUI Vue headlessui-vue
30 accessible Vue component examples
Key tools: list_frameworks , list_components(framework) , get_component(framework, ...) , search_components(query) , get_component_by_path(path)
Use search_components(query: "keyword") to find components across all libraries, or filter by framework.