Best Practices
Write maintainable Tailwind at scale — organization conventions, team standards, accessibility patterns, and anti-patterns to avoid.
Component Organization
Organize by feature or component type, not by file type. Colocate component styles (Tailwind classes) with component logic. A Button.tsx contains both the JSX and its Tailwind classes.
Extract to shared components when patterns repeat three or more times. Keep one-off layouts inline — not everything needs abstraction.
- Use ui/ for design system primitives used across features
- Feature components can compose ui/ primitives freely
- Avoid a separate styles/ directory — classes live in components
components/ ├── ui/ # Shared primitives (Button, Input, Card) ├── features/ # Feature-specific components │ ├── auth/ │ └── dashboard/ └── layouts/ # Page layout wrappers
Class Order and Readability
Sort classes consistently with Prettier tailwindcss plugin: layout, spacing, sizing, typography, colors, effects, states. Consistent ordering makes diffs readable and classes scannable.
Break long class strings across lines aligned with JSX structure. Group related utilities on the same line: all spacing together, all colors together.
// Prettier sorts automatically: <div class="flex items-center gap-4 px-6 py-4 bg-white rounded-xl shadow-sm hover:shadow-md transition-shadow">
Team Conventions
Document decisions: when to extract components, naming conventions, color token usage, and responsive patterns. Enforce with ESLint plugin eslint-plugin-tailwindcss and PR review checklists.
Agree on a component library foundation (shadcn/ui, Headless UI) to avoid reinventing accessible patterns. Customize the foundation with your brand tokens.
// .eslintrc — tailwindcss plugin catches errors
{
"plugins": ["tailwindcss"],
"rules": {
"tailwindcss/classnames-order": "warn",
"tailwindcss/no-contradicting-classname": "error"
}
}Accessibility Standards
Tailwind does not automatically make UI accessible. You must add focus styles, ARIA attributes, semantic HTML, and sufficient color contrast manually.
Use sr-only for screen reader text on icon buttons. Ensure focus-visible:ring is on all interactive elements. Test with keyboard navigation and screen readers.
- Never use outline-none without focus-visible:ring replacement
- Color contrast must meet WCAG AA in both light and dark modes
- Use semantic HTML elements before adding ARIA roles
<button aria-label="Close dialog"
class="focus-visible:ring-2 focus-visible:ring-blue-500
focus-visible:outline-none">
<XIcon class="h-5 w-5" />
<span class="sr-only">Close</span>
</button>Anti-patterns to Avoid
Do not use @apply everywhere — it reintroduces CSS specificity issues. Do not build class names dynamically with template literals. Do not ignore the content config — missing classes in production is a common failure.
Avoid excessively long class strings (20+ utilities) without extraction. If a class list exceeds three lines, create a component. Do not fight Tailwind with important overrides unless integrating third-party CSS.
// Bad: dynamic class names (purged in production)
<div class={'bg-' + color + '-500'} />
// Good: complete class names
const colors = { red: 'bg-red-500', blue: 'bg-blue-500' };
<div class={colors[status]} />