Tailwind CSS Patterns I Use on Every Project
CSS variables for theming, cn() for conditional classes, group and peer for state-driven styling, and arbitrary values for the one-off cases — the Tailwind I actually ship.
The short answer: The Tailwind patterns I ship on every project are CSS variables for theming (not dark: on every element), a cn() helper for conditional classes, group/peer for state-driven styling, and arbitrary values for genuine one-offs. Lean on those four and the "ugly markup" complaint mostly disappears.
Tailwind gets criticized for "ugly" markup, and the criticism is fair when you fight the framework. After using it across a dozen production apps, the difference between a Tailwind codebase that's a joy and one that's a mess comes down to a handful of patterns. Here are the ones I reach for on every project.
Theme with CSS variables, not dark: everywhere
Sprinkling dark:bg-zinc-900 dark:text-zinc-100 on every element is how dark mode becomes unmaintainable. Instead, map Tailwind colors to CSS variables and flip the variables once.
Now bg-background text-foreground is theme-aware with zero dark: variants. Your components stop caring about the theme entirely.
A cn() helper for conditional classes
String concatenation for conditional classes is error-prone and leaves duplicate/conflicting utilities. clsx composes the conditions and tailwind-merge resolves conflicts (so a later px-6 beats an earlier px-4). Wrap both once.
group and peer for state-driven styling
Reaching for JavaScript to style a child on parent hover is a waste. group/group-hover and peer/peer-checked cover most interactive styling with pure CSS.
The arrow slides in on card hover with no state, no event handler, no re-render.
Arbitrary values for the genuine one-offs
Don't invent a config token for a value used once. blur-[100px], top-[15px], w-[14rem], grid-cols-[200px_1fr] keep the design-system config lean and the one-off local to where it's needed. Reserve theme.extend for values that repeat.
line-clamp and aspect-* instead of hacks
Two utilities that replaced piles of fragile CSS: line-clamp-2 truncates multi-line text with an ellipsis (no more overflow + -webkit-box incantations), and aspect-video / aspect-square hold image ratios without padding-top tricks. Both ship in core now.
The meta-lesson
Tailwind rewards you for building a small vocabulary — a cn() helper, a variable-driven palette, a few extracted components for the truly repeated patterns — and then composing utilities freely everywhere else. The teams that hate it are usually the ones re-abstracting every three-utility combo into a @apply class, which just reinvents the CSS files they were trying to escape. Let the utilities be utilities.