Best Practices & Anti-Patterns
When to Use
Reference before code review, architecture decisions, or when evaluating whether a Tailwind implementation is idiomatic.
Core Philosophy: Utility-First Means Utilities First
Utility-first doesn't mean "utilities only" — it means utilities are the default, and abstractions are earned through genuine reuse. The wrong instinct is to immediately reach for @apply or component classes. The right instinct is to write utilities in markup until a real pattern emerges across multiple files.
Development Standards
| Practice | Do | Don't |
|---|---|---|
| Class organization | Follow order: layout → sizing → spacing → typography → color → effects → state | Random order (maintenance nightmare) |
| Token usage | All colors/sizes from @theme tokens |
Raw hex values (bg-[#4f46e5]) in reusable components |
| Responsive | Mobile-first with sm:, md: prefixes |
Desktop-first with max-* as primary |
| Focus styles | focus-visible: for keyboard rings |
focus: (shows on mouse clicks) or outline-none alone |
| Dynamic values | Static class lookup maps | String interpolation (bg-${color}-500) |
Anti-Patterns with WHY
-
Recreating Bootstrap with @apply —
@applycompiles utilities back into CSS, destroying the single-source-of-truth benefit. You now maintain class names AND utilities. When a color changes, you change the token AND hunt down every@applyreference. -
Fighting the spacing scale — Tailwind's 4px scale (
p-1=4px,p-4=16px,p-8=32px) is intentional. Using arbitrary values (p-[13px]) constantly signals a mismatch between design and the token system. Fix the token system, not the utilities. -
Inconsistent color references — mixing
bg-blue-500,bg-[#3b82f6], andbg-primaryfor the same color across a project. Pick one: use the token everywhere. Inconsistency means three places to update when the brand color changes. -
Ignoring the component extraction signal — if you copy-paste the same 8 utility classes more than twice across different files, that's your signal to create a framework component. The markup IS the component contract; don't extract to CSS.
-
Arbitrary values for design system values —
w-[340px]used consistently means340pxbelongs in@theme. Arbitrary values are for truly one-off values that will never repeat. -
Overloading @layer components — if you find yourself with 20+ classes in
@layer components, you've rebuilt Bootstrap. Step back: these should be framework components (React/Vue/Twig/etc.) or they should be inline utilities.
Security Standards
Tailwind itself has no server-side rendering or XSS vectors, but the surrounding implementation does:
- Never build Tailwind class names from user input —
bg-${userColorPreference}-500used with Tailwind's safelist creates an attack surface where users can influence what CSS is generated or cached - Be careful with class injection — if user-provided data is rendered as CSS class attributes in HTML, sanitize it; even non-existent class names can be used for CSS injection in frameworks that evaluate arbitrary selectors
- Token values from untrusted sources — if design tokens are loaded from an API or CMS, validate color values before injecting into
@theme; malformed oklch values won't cause XSS but may break rendering
Accessibility Standards
These are non-negotiable minimums — not optional:
- Always use
focus-visible:notfocus:for keyboard focus rings —focus:triggers on mouse clicks, degrading UX;focus-visible:only activates for keyboard navigation - Never use
outline-nonewithout a replacement —focus-visible:outline-none focus-visible:ring-2is acceptable; bareoutline-nonemakes the page unusable for keyboard users - Color contrast — Tailwind's default palette satisfies WCAG AA at most shade combinations, but verify:
gray-500onwhiteis ~4.4:1 (borderline);gray-600onwhiteis ~5.5:1 (clear pass) - Screen reader utilities — use
sr-onlyfor accessible labels on icon-only buttons; usenot-sr-onlyto conditionally reveal them - Motion — wrap animations in
motion-safe:or test withmotion-reduce:hidden
Performance Standards
- Avoid N+1 class generation — safelisting (
@source inline) generates CSS at build time; don't safelist entire color scales when you only need 3 shades - The real performance cost is CSS size — Tailwind's output is already tree-shaken; the developer cost is class-name verbosity, not runtime performance
- Container queries are lightweight —
@containeradds no JavaScript; it's pure CSS. Use freely. - Prefer CSS transitions over JavaScript animations —
transition-colors duration-200is faster than JS-driven class toggling for simple state changes
Common Mistakes
- Using Tailwind like a utility-class version of Bootstrap — the mental model is wrong; utilities are composable primitives, not a component library
- Starting with component extraction before seeing real duplication — YAGNI applies; inline utilities until duplication is proven
- Over-configuring
@themewith values that never generate utilities — if you define--color-brand-150but never usebg-brand-150, you've added dead tokens
See Also
- Accessibility
- Performance & Optimization
- Reference: https://www.wisp.blog/blog/best-practices-for-using-tailwind-css-in-large-projects