Back to articles
Tailwind CSSFrontend

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.

Yash Thakur
4 min read
Tailwind CSS Patterns I Use on Every Project

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.

Written by Yash Thakur

Senior React Developer · 8+ years building for the web

More articles

Keep reading