Tailwind CSS Expert
Overview
Advanced expertise in Tailwind CSS — from utility-first fundamentals and custom theming to component patterns, Tailwind v4, dark mode, and integration with shadcn/ui and Radix UI.
1. Core Utility Patterns
Layout
<!-- Flexbox -->
<div class="flex items-center justify-between gap-4">
<div class="flex flex-col gap-2">
<div class="flex flex-wrap gap-2">
<!-- Grid -->
<div class="grid grid-cols-3 gap-6">
<div class="grid grid-cols-[1fr_2fr_1fr] gap-4"> <!-- arbitrary -->
<div class="grid grid-cols-12 col-span-4">
<!-- Positioning -->
<div class="relative">
<div class="absolute inset-0"> <!-- fill parent -->
<div class="absolute top-4 right-4">
<div class="fixed bottom-6 right-6">
<div class="sticky top-0 z-50">
Spacing
<!-- Padding -->
<div class="p-4 px-6 py-3 pt-2 pb-4 pl-0 pr-0">
<!-- Margin -->
<div class="m-4 mx-auto mt-8 mb-4 -mt-2"> <!-- negative: -mt-2 -->
<!-- Space between children -->
<div class="space-y-4"> <!-- margin-top on all but first child -->
<div class="space-x-2">
Sizing
<div class="w-full w-1/2 w-64 w-[340px] w-screen">
<div class="h-full h-screen h-64 h-[calc(100vh-4rem)]">
<div class="min-h-screen max-w-7xl mx-auto">
<div class="size-10"> <!-- w-10 h-10 shorthand (v3.4+) -->
Typography
<p class="text-sm text-base text-lg text-xl text-2xl text-[15px]">
<h1 class="text-4xl font-bold tracking-tight leading-tight">
<p class="text-gray-600 text-center truncate line-clamp-2">
<span class="font-semibold italic underline decoration-2 decoration-blue-500">
2. Responsive Design
Mobile-first breakpoints: sm (640px), md (768px), lg (1024px), xl (1280px), 2xl (1536px)
<!-- Stack on mobile, side-by-side on md+ -->
<div class="flex flex-col md:flex-row gap-4">
<!-- Hidden on mobile, visible on lg+ -->
<aside class="hidden lg:block">
<!-- Grid: 1 col → 2 col → 3 col -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Text size responsive -->
<h1 class="text-2xl md:text-4xl lg:text-5xl font-bold">
<!-- Custom breakpoints in tailwind.config -->
<!-- screens: { 'xs': '480px', '3xl': '1920px' } -->
<div class="xs:flex 3xl:grid-cols-5">
3. Dark Mode
// tailwind.config.ts
export default {
darkMode: 'class', // toggle with .dark class on <html>
// darkMode: 'media', // use prefers-color-scheme
}
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<button class="bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700">
// Toggle dark mode
const toggleDark = () => document.documentElement.classList.toggle('dark')
4. State Variants
<!-- Hover, focus, active -->
<button class="bg-blue-500 hover:bg-blue-600 active:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
<!-- Disabled -->
<button class="disabled:opacity-50 disabled:cursor-not-allowed">
<!-- Group hover — parent hover affects child -->
<div class="group">
<p class="text-gray-500 group-hover:text-blue-600 transition-colors">
<svg class="opacity-0 group-hover:opacity-100 transition-opacity">
<!-- Peer — sibling state -->
<input class="peer" type="checkbox" />
<label class="peer-checked:text-blue-600 peer-checked:font-semibold">
<!-- Has — parent responds to child state (v3.4+) -->
<div class="has-[:checked]:bg-blue-50">
<input type="checkbox" />
</div>
<!-- Focus-within -->
<div class="border focus-within:border-blue-500 focus-within:ring-2">
<input class="outline-none" />
</div>
5. Custom Theme (tailwind.config.ts)
import type { Config } from 'tailwindcss'
export default {
content: ['./src/**/*.{ts,tsx,html}'],
theme: {
// Override completely
fontFamily: {
sans: ['Inter', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
extend: {
// Extend existing theme
colors: {
brand: {
50: '#eff6ff',
500: '#3b82f6',
900: '#1e3a8a',
},
surface: 'hsl(var(--surface))', // CSS variable
},
spacing: {
'18': '4.5rem',
'128': '32rem',
},
borderRadius: {
'4xl': '2rem',
},
animation: {
'fade-in': 'fadeIn 0.3s ease-in-out',
'slide-up': 'slideUp 0.4s ease-out',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideUp: {
'0%': { transform: 'translateY(20px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
},
screens: {
'xs': '480px',
'3xl': '1920px',
},
},
},
plugins: [
require('@tailwindcss/typography'),
require('@tailwindcss/forms'),
require('@tailwindcss/aspect-ratio'),
],
} satisfies Config
6. CSS Variables + Tailwind (Design Tokens)
/* globals.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
}
}
// tailwind.config.ts
colors: {
background: 'hsl(var(--background) / <alpha-value>)',
foreground: 'hsl(var(--foreground) / <alpha-value>)',
primary: {
DEFAULT: 'hsl(var(--primary) / <alpha-value>)',
foreground: 'hsl(var(--primary-foreground) / <alpha-value>)',
},
}
7. cn Utility (clsx + tailwind-merge)
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// Usage — merges correctly, later classes win
cn('px-4 py-2', 'px-6') // → 'py-2 px-6'
cn('text-red-500', condition && 'text-blue-500')
cn({ 'opacity-50 cursor-not-allowed': disabled })
8. Class Variance Authority (CVA) — Component Variants
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
// Base styles
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
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',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2 text-sm',
sm: 'h-8 px-3 text-xs',
lg: 'h-12 px-6 text-base',
icon: 'h-10 w-10',
},
},
defaultVariants: { variant: 'default', size: 'default' },
}
)
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
function Button({ className, variant, size, ...props }: ButtonProps) {
return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />
}
9. shadcn/ui
# Init (Next.js)
npx shadcn@latest init
# Add components
npx shadcn@latest add button dialog form input table
- Components live in
components/ui/— fully owned & editable - Built on Radix UI primitives + Tailwind + CVA
- Theming via CSS variables in
globals.css - Registry:
button,card,dialog,dropdown-menu,form,input,select,sheet,table,toast,tooltip, etc.
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
<Dialog open={open}
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Profile</DialogTitle>
</DialogHeader>
<Button variant="outline" size="sm">Cancel</Button>
</DialogContent>
</Dialog>
10. Tailwind v4 (2025)
/* No tailwind.config.js — configure in CSS */
@import "tailwindcss";
@theme {
--color-brand-500: #3b82f6;
--font-sans: "Inter", sans-serif;
--radius-lg: 0.75rem;
}
/* Custom utilities */
@utility container {
margin-inline: auto;
padding-inline: 1rem;
max-width: 80rem;
}
Key v4 changes:
- Config moved to CSS
@themeblock (no JS config needed) @utilityreplaces plugin API for custom utilities- Lightning CSS compiler — faster builds, no PostCSS required
@import "tailwindcss"instead of@tailwind base/components/utilities- CSS variables are first-class —
--color-*,--font-*,--spacing-* - New
inset-shadow-*,field-sizing-*,not-*variant utilities
11. Animations & Transitions
<!-- Transitions -->
<div class="transition-all duration-300 ease-in-out">
<div class="transition-colors duration-200">
<div class="transition-transform hover:-translate-y-1">
<!-- Transforms -->
<div class="hover:scale-105 hover:-translate-y-2 hover:rotate-3">
<!-- Animate (pulse, spin, bounce, ping) -->
<div class="animate-spin"> <!-- loading spinner -->
<div class="animate-pulse"> <!-- skeleton loading -->
<div class="animate-bounce">
<div class="animate-ping"> <!-- notification dot -->
12. Arbitrary Values & @apply
<!-- Arbitrary values — any CSS value -->
<div class="w-[742px] top-[117px] bg-[#1da1f2] text-[22px]">
<div class="grid-cols-[repeat(auto-fill,minmax(200px,1fr))]">
<div class="shadow-[0_4px_30px_rgba(0,0,0,0.1)]">
<div class="bg-[url('/hero.jpg')] bg-cover bg-center">
<!-- Arbitrary properties (v3.3+) -->
<div class="[mask-type:luminance] [scrollbar-width:none]">
<!-- @apply in CSS (use sparingly — prefer utilities) -->
@layer components {
.btn-primary {
@apply inline-flex items-center px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors;
}
}
13. Typography Plugin (@tailwindcss/typography)
<article class="prose prose-lg dark:prose-invert max-w-none">
<!-- Renders markdown/HTML with beautiful typography -->
<h1>Title</h1>
<p>Content with <a href="#">links</a>, <code>code</code>, etc.</p>
</article>
<!-- Customize -->
<article class="prose prose-headings:font-bold prose-a:text-blue-600">
Core Competency Summary
- Build responsive, accessible UIs using Tailwind utility classes
- Customize themes with
tailwind.config.tsand CSS variables - Implement dark mode, state variants (
hover,focus,group,peer) - Create reusable component variants with CVA and
cnutility - Work with shadcn/ui components built on Radix UI + Tailwind
- Use Tailwind v4 CSS-first configuration with
@theme - Apply animations, transitions, and arbitrary values for pixel-perfect UIs