Loading...
Loading...
Tailwind CSS 4 patterns: semantic classes, cn() utility, dynamic styling, library exceptions. Trigger: When styling with Tailwind, using className, conditional styles, or dark mode.
npx skill4agent add fearovex/claude-config tailwind-4// ✅ Use semantic classes
<div className="bg-primary text-white" />
<div className="border-border" />
<div className="text-foreground bg-background" />
// ❌ Never var() in className
<div className="bg-[var(--color-primary)]" />
<div className="text-[var(--foreground)]" />// ✅ Semantic
<p className="text-white bg-slate-900" />
<p className="text-gray-500" />
// ❌ Avoid hex in className when an equivalent exists
<p className="text-[#ffffff] bg-[#0f172a]" />import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// ✅ Use cn() for conditionals and conflicts
<button
className={cn(
'px-4 py-2 rounded-md font-medium',
variant === 'primary' && 'bg-primary text-white',
variant === 'ghost' && 'bg-transparent hover:bg-accent',
disabled && 'opacity-50 cursor-not-allowed',
className // allows external override
)}
/>
// ✅ Static classes — no cn() needed
<div className="flex items-center gap-4 p-6" />import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
{
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',
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4',
lg: 'h-12 px-6 text-lg',
},
},
defaultVariants: {
variant: 'default',
size: 'md',
},
}
);
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
function Button({ variant, size, className, ...props }: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
);
}// ✅ Dark mode with Tailwind classes
<div className="bg-white dark:bg-gray-900">
<p className="text-gray-900 dark:text-gray-100">Content</p>
<button className="bg-blue-600 dark:bg-blue-500 hover:bg-blue-700 dark:hover:bg-blue-400">
Action
</button>
</div>
// CSS config (tailwind.config.ts)
export default {
darkMode: 'class', // or 'media'
// ...
}// Mobile-first with breakpoints
<div className="
flex flex-col // mobile: column
md:flex-row // tablet: row
lg:grid lg:grid-cols-3 // desktop: 3-col grid
gap-4
">
<Card />
<Card />
<Card />
</div>// ✅ For libraries that don't support className, use CSS var constants
const CHART_COLORS = {
primary: 'var(--color-primary)',
secondary: 'var(--color-secondary)',
muted: 'var(--color-muted)',
} as const;
<LineChart>
<Line stroke={CHART_COLORS.primary} />
</LineChart>// ✅ style prop for runtime calculations
<div style={{ width: `${percentage}%` }} className="bg-primary h-2 rounded" />
// ✅ CSS custom properties for theming
<div
style={{ '--card-cols': columns } as React.CSSProperties}
className="grid grid-cols-[repeat(var(--card-cols),1fr)]"
/>// ❌ Breaks Tailwind's optimizer
<div className="bg-[var(--primary)] text-[var(--fg)]" />
// ✅
<div className="bg-primary text-foreground" />// ❌ Can generate invalid classes
<div className={'text-sm ' + (active ? 'text-blue-600' : 'text-gray-500')} />
// ✅
<div className={cn('text-sm', active ? 'text-blue-600' : 'text-gray-500')} />// ❌ Unnecessary
<div className={cn('flex items-center gap-4')} />
// ✅
<div className="flex items-center gap-4" />| Task | Pattern |
|---|---|
| Semantic color | |
| Conditional | |
| Variants | |
| Dark mode | |
| Responsive | |
| Runtime value | |
| External override | Accept and apply |
cn()@themetailwind.config.js@apply'text-red-500'`text-${color}-500`