Tailwind CSS 4 Development Guidelines
Modern utility-first CSS with CSS variables, container queries, and improved DX
Core Principles
- •Utility-First: Compose designs with utility classes
- •Mobile-First: Build responsive designs from small to large
- •CSS Variables: Use Tailwind's CSS variable system
- •No Inline Styles: Use Tailwind classes exclusively
- •Consistent Spacing: Use Tailwind's spacing scale
Project Setup
Configuration (Tailwind 4)
typescript
// postcss.config.mjs
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};
Global Styles
css
/* src/app/(frontend)/globals.css */
@import "tailwindcss";
@theme {
/* Custom design tokens */
--color-primary: #3b82f6;
--color-primary-foreground: #ffffff;
--font-sans: "Inter", system-ui, sans-serif;
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
}
/* Custom base styles */
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground antialiased;
}
}
Design Tokens (CSS Variables)
Color System
css
@theme {
/* Light mode */
--color-background: 0 0% 100%;
--color-foreground: 0 0% 3.9%;
--color-card: 0 0% 100%;
--color-card-foreground: 0 0% 3.9%;
--color-primary: 221.2 83.2% 53.3%;
--color-primary-foreground: 210 40% 98%;
--color-secondary: 210 40% 96.1%;
--color-secondary-foreground: 222.2 47.4% 11.2%;
--color-muted: 210 40% 96.1%;
--color-muted-foreground: 215.4 16.3% 46.9%;
--color-destructive: 0 84.2% 60.2%;
--color-destructive-foreground: 210 40% 98%;
--color-border: 214.3 31.8% 91.4%;
--color-input: 214.3 31.8% 91.4%;
--color-ring: 221.2 83.2% 53.3%;
}
@media (prefers-color-scheme: dark) {
@theme {
--color-background: 0 0% 3.9%;
--color-foreground: 0 0% 98%;
--color-card: 0 0% 3.9%;
--color-card-foreground: 0 0% 98%;
--color-primary: 217.2 91.2% 59.8%;
--color-primary-foreground: 222.2 47.4% 11.2%;
/* ... more dark mode colors */
}
}
Usage
tsx
// ✅ Use semantic color classes <div className="bg-background text-foreground"> <button className="bg-primary text-primary-foreground"> <div className="border-border"> // ❌ Avoid hardcoded colors <div className="bg-white text-black"> <button className="bg-blue-500 text-white">
Layout Patterns
Container
tsx
// Max-width centered container
<div className="container mx-auto px-4">
{/* Content */}
</div>
// Full width with padding
<div className="px-4 md:px-6 lg:px-8">
{/* Content */}
</div>
Flexbox
tsx
// Row with gap
<div className="flex gap-4">
<div>Item 1</div>
<div>Item 2</div>
</div>
// Column centered
<div className="flex flex-col items-center justify-center min-h-screen">
<h1>Centered Content</h1>
</div>
// Space between
<div className="flex items-center justify-between">
<div>Left</div>
<div>Right</div>
</div>
// Wrap
<div className="flex flex-wrap gap-2">
{items.map(item => <Tag key={item.id}>{item.name}</Tag>)}
</div>
Grid
tsx
// Responsive grid
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
// Auto-fit grid
<div className="grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-4">
{/* Cards automatically wrap */}
</div>
// Complex grid
<div className="grid grid-cols-12 gap-4">
<div className="col-span-12 md:col-span-8">Main</div>
<div className="col-span-12 md:col-span-4">Sidebar</div>
</div>
Responsive Design (Mobile-First)
Breakpoints
- •
sm: 640px (small tablets) - •
md: 768px (tablets) - •
lg: 1024px (laptops) - •
xl: 1280px (desktops) - •
2xl: 1536px (large desktops)
Pattern
tsx
// Stack on mobile, row on desktop
<div className="flex flex-col md:flex-row gap-4">
<div className="w-full md:w-1/2">Column 1</div>
<div className="w-full md:w-1/2">Column 2</div>
</div>
// Responsive text
<h1 className="text-2xl md:text-4xl lg:text-5xl font-bold">
Heading
</h1>
// Responsive padding
<div className="p-4 md:p-6 lg:p-8">
{/* Content */}
</div>
// Hide/show based on screen size
<div className="hidden md:block">Desktop only</div>
<div className="block md:hidden">Mobile only</div>
Component Patterns
Button
tsx
// Primary button <button className=" inline-flex items-center justify-center gap-2 px-4 py-2 rounded-md bg-primary text-primary-foreground font-medium text-sm hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:pointer-events-none transition-colors "> Click me </button> // Button variants <button className="bg-secondary text-secondary-foreground hover:bg-secondary/80"> Secondary </button> <button className="bg-destructive text-destructive-foreground hover:bg-destructive/90"> Delete </button> <button className="border border-input bg-background hover:bg-accent hover:text-accent-foreground"> Outline </button>
Card
tsx
<div
className="
rounded-lg border bg-card text-card-foreground shadow-sm
"
>
<div className="p-6 space-y-1.5">
<h3 className="text-2xl font-semibold leading-none tracking-tight">
Card Title
</h3>
<p className="text-sm text-muted-foreground">Card Description</p>
</div>
<div className="p-6 pt-0">{/* Card content */}</div>
<div className="flex items-center p-6 pt-0">{/* Card footer */}</div>
</div>
Input
tsx
<input
type="text"
className="
flex h-10 w-full rounded-md
border border-input
bg-background
px-3 py-2
text-sm
ring-offset-background
file:border-0 file:bg-transparent file:text-sm file:font-medium
placeholder:text-muted-foreground
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
disabled:cursor-not-allowed disabled:opacity-50
"
placeholder="Enter text..."
/>
Badge
tsx
<span className=" inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold bg-primary text-primary-foreground "> Badge </span> // Variants <span className="bg-secondary text-secondary-foreground">Secondary</span> <span className="bg-destructive text-destructive-foreground">Error</span> <span className="border border-input bg-background">Outline</span>
Dark Mode
Using CSS Variables (Automatic)
tsx
// Colors adapt automatically via CSS variables
<div className="bg-background text-foreground">
<button className="bg-primary text-primary-foreground">
Works in both themes
</button>
</div>
Manual Toggle (if needed)
tsx
"use client";
import { useTheme } from "next-themes";
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<button
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
className="rounded-md p-2 hover:bg-accent"
>
{theme === "dark" ? "🌞" : "🌙"}
</button>
);
}
Animations & Transitions
Hover Effects
tsx
// Smooth transitions <button className=" bg-primary hover:bg-primary/90 transition-colors duration-200 "> Hover me </button> // Scale on hover <div className=" transform hover:scale-105 transition-transform duration-200 "> Card </div> // Shadow on hover <div className=" shadow-md hover:shadow-lg transition-shadow duration-200 "> Elevated card </div>
Loading States
tsx
// Pulse animation <div className="animate-pulse"> <div className="h-4 bg-muted rounded w-3/4 mb-2"></div> <div className="h-4 bg-muted rounded w-1/2"></div> </div> // Spin animation <div className="animate-spin h-5 w-5 border-2 border-primary border-t-transparent rounded-full"></div>
Custom Animations
css
@layer utilities {
@keyframes slide-in {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
.animate-slide-in {
animation: slide-in 0.3s ease-out;
}
}
Utility Patterns
Spacing
tsx
// Consistent spacing scale (4px base)
<div className="space-y-4"> {/* 16px gap between children */}
<div className="space-x-2"> {/* 8px gap between children */}
<div className="p-4"> {/* 16px padding */}
<div className="px-6 py-4"> {/* 24px horizontal, 16px vertical */}
<div className="gap-4"> {/* 16px gap in flex/grid */}
Typography
tsx
// Headings <h1 className="text-4xl font-bold tracking-tight"> Main Title </h1> <h2 className="text-3xl font-semibold"> Section Title </h2> <h3 className="text-2xl font-medium"> Subsection </h3> // Body text <p className="text-base text-muted-foreground leading-relaxed"> Regular paragraph text </p> // Small text <span className="text-sm text-muted-foreground"> Helper text </span> // Truncate <p className="truncate"> This text will be truncated with ellipsis... </p> // Line clamp <p className="line-clamp-3"> This text will be limited to 3 lines with ellipsis... </p>
Focus States
tsx
// Always include focus states for accessibility <button className=" focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 "> Accessible button </button> <input className=" focus:outline-none focus:ring-2 focus:ring-ring "/>
Container Queries (Tailwind 4)
tsx
// Enable container queries
<div className="@container">
<div className="@sm:grid-cols-2 @lg:grid-cols-3 grid gap-4">
{/* Responds to container size, not viewport */}
</div>
<h2 className="text-xl @md:text-2xl @lg:text-3xl">Responsive to container</h2>
</div>
Advanced Patterns
Group Hover
tsx
<div className="group hover:bg-accent rounded-lg p-4">
<h3 className="group-hover:text-primary">
Title changes color on parent hover
</h3>
<button className="opacity-0 group-hover:opacity-100">
Appears on hover
</button>
</div>
Peer Modifier
tsx
<div>
<input type="checkbox" className="peer sr-only" id="terms" />
<label
htmlFor="terms"
className="
peer-checked:bg-primary peer-checked:text-primary-foreground
peer-focus:ring-2 peer-focus:ring-ring
"
>
Accept terms
</label>
</div>
Arbitrary Values
tsx
// Use sparingly, prefer design tokens <div className="w-[350px]">Fixed width</div> <div className="top-[117px]">Specific position</div> <div className="bg-[#1da1f2]">Brand color</div>
Data Attributes
tsx
// State-based styling
<div
data-state={isOpen ? "open" : "closed"}
className="
data-[state=open]:bg-accent
data-[state=closed]:bg-muted
"
>
Content
</div>
Best Practices
✅ Do:
tsx
// Extract common patterns to components <Button variant="primary" size="lg">Click me</Button> // Use semantic colors <div className="bg-background text-foreground"> // Compose utilities <div className="flex items-center justify-between gap-4 p-4 rounded-lg border"> // Responsive design <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
❌ Don't:
tsx
// Too many utilities (extract to component)
<div className="flex items-center justify-center p-4 m-2 bg-white text-black rounded-lg shadow-md hover:shadow-lg transition-shadow border border-gray-200">
// Inline styles
<div style={{ padding: '16px', margin: '8px' }}>
// Hardcoded colors
<div className="bg-blue-500 text-white">
// Arbitrary values everywhere
<div className="w-[234px] h-[567px] mt-[23px]">
Performance Tips
- •Use PurgeCSS (automatic in production)
- •Avoid @apply in components (use utilities directly)
- •Group related utilities for readability
- •Use CSS variables for theme values
Component Library Integration (shadcn/ui)
Components use Tailwind utilities:
tsx
// Example: Button component using Tailwind
import { cn } from "@/lib/utils";
const buttonVariants = {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 px-3",
lg: "h-11 px-8",
},
};
export function Button({
variant = "default",
size = "default",
className,
...props
}) {
return (
<button
className={cn(
"inline-flex items-center justify-center rounded-md font-medium transition-colors",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
"disabled:pointer-events-none disabled:opacity-50",
buttonVariants.variant[variant],
buttonVariants.size[size],
className,
)}
{...props}
/>
);
}