· 5 min read
Top 5 Mistakes Newcomers Make with Tailwind CSS (And How to Avoid Them)
A practical guide for developers new to Tailwind CSS: five common mistakes, why they happen, and clear, copy-paste solutions to fix them fast - including config tips, component extraction patterns, build optimizations, and accessibility reminders.
Introduction
Tailwind CSS is incredibly powerful - but its utility-first approach can trip up newcomers. Below are the top 5 mistakes I see time and again, why they cause problems, and concrete fixes you can apply right away.
Mistake 1 - Treating Tailwind as “inline styles” instead of a design system
Why it happens
- Newcomers often write large, page-specific utility lists (a.k.a. very long class attributes) for every element.
- This works at first but quickly becomes repetitive and brittle.
What goes wrong
- Duplicate logic across many elements.
- Harder to maintain and refactor.
- Hard to keep design consistent.
How to avoid it
- Extract shared patterns into named components using one of these approaches:
- CSS components with @apply
- Reusable UI components (React/Vue/Svelte) that take props
- Utility extraction tools (e.g., using a small wrapper or a class-name helper)
Examples
- Using @apply in a stylesheet to create a reusable class (recommended for non-dynamic variants):
/* src/styles/components.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply inline-flex items-center px-4 py-2 rounded-md text-sm font-medium;
}
.btn-primary {
@apply btn bg-blue-600 text-white hover:bg-blue-700;
}
}
Then in markup:
<button class="btn-primary">Save</button>
- Extracting into a React component with clsx (for conditional classes):
import clsx from 'clsx';
export default function Button({
children,
variant = 'primary',
className,
...props
}) {
const base =
'inline-flex items-center px-4 py-2 rounded-md text-sm font-medium';
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
ghost: 'bg-transparent text-gray-800 hover:bg-gray-100',
};
return (
<button className={clsx(base, variants[variant], className)} {...props}>
{children}
</button>
);
}
Resources: see the Tailwind docs on @apply.
Mistake 2 - Not configuring Tailwind’s theme and relying only on utility defaults
Why it happens
- Beginners copy-paste utilities and don’t customize the design tokens in tailwind.config.js.
What goes wrong
- Inconsistent colors, spacing, typography across the project.
- Repetitive use of arbitrary values which undermines design system benefits.
How to avoid it
- Create a small, opinionated tailwind.config.js early. Add your brand colors, spacing scale, and font stacks.
- Use the theme tokens throughout components so changes are global and predictable.
Example minimal config
// tailwind.config.js
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx,html}'],
theme: {
extend: {
colors: {
brand: {
50: '#f5f9ff',
500: '#2563eb',
700: '#1e40af',
},
},
spacing: {
72: '18rem',
},
},
},
plugins: [],
};
Tip: use semantic names when appropriate (e.g., btn, card) on top of color tokens.
Docs: Configuration.
Mistake 3 - Misunderstanding build and purge (content) settings - huge CSS or missing classes
Why it happens
- New users copy config without setting the content (formerly purge) paths or confuse dev vs production builds.
- With dynamic classes (e.g., constructed class strings), the extractor may miss classes causing them to be removed in production.
What goes wrong
- Production CSS remains huge (if purge is disabled) or necessary utility classes are missing (if the extractor can’t see them).
How to avoid it
- Always set the content array to include every file that contains Tailwind classes (JSX, Vue, Svelte, HTML, etc.).
- For dynamic classes, use safe patterns so Tailwind can detect them, or whitelist classes. Prefer stable patterns or CSS/JS component extraction.
- Use the recommended production build step (Tailwind automatically removes unused styles when used with a proper bundler and the content config).
Example content config for a Next.js app
// tailwind.config.js
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./app/**/*.{js,ts,jsx,tsx}',
],
theme: {
/* ... */
},
};
If you build class names dynamically, prefer mapping objects or small helper functions:
// BAD: `bg-${color}-500` can't be detected by Tailwind's static extractor
const color = 'blue';
<div className={`bg-${color}-500`}></div>;
// BETTER: map values explicitly
const bgMap = { blue: 'bg-blue-500', red: 'bg-red-500' };
<div className={bgMap[color]}></div>;
Docs: Optimizing for production
Mistake 4 - Ignoring mobile-first responsive mindset and variant ordering
Why it happens
- Coming from Bootstrap or another framework, newcomers sometimes assume Tailwind’s breakpoint utilities are applied differently.
What goes wrong
- Unexpected layout on small screens because they wrote styles for large screens first or misused breakpoints.
How to avoid it
- Tailwind is mobile-first: base utilities apply to all sizes; prefixed utilities (sm:, md:, lg:, xl:, 2xl:) apply at that breakpoint and up.
- Write base (mobile) styles first, then layer breakpoints for larger screens.
Good example
<!-- Mobile-first -->
<div class="p-3 text-sm md:p-6 md:text-base lg:p-8">Content</div>
Bad pattern (hard to reason about):
<!-- Don't start with large styles then try to override them for small screens -->
<div class="lg:p-8 p-3"></div>
Also be aware that ordering of breakpoint utilities in the class attribute does not matter - Tailwind generates the CSS by breakpoint - but your mental model should be mobile-first.
Docs: Responsive design
Mistake 5 - Forgetting semantics and accessibility because utilities make layout easy
Why it happens
- It’s tempting to use divs everywhere and style them with utilities.
What goes wrong
- Poor accessibility, worse SEO, and brittle UX for keyboard / screen-reader users.
How to avoid it
- Always choose semantic elements where appropriate: use
- Use ARIA attributes when semantics need augmentation, not as a replacement for semantic markup.
- Make focus states visible (Tailwind includes focus utilities) and test keyboard navigation.
Examples
<!-- Semantic button with visible focus ring -->
<button class="btn-primary focus:outline-none focus:ring-4 focus:ring-blue-200">
Save
</button>
<!-- Use proper headings -->
<main>
<h1 class="text-2xl font-bold">Page title</h1>
<section aria-labelledby="features-heading">
<h2 id="features-heading">Features</h2>
<!-- ... -->
</section>
</main>
Helpful extensions and tools
- Tailwind CSS docs - indispensable: https://tailwindcss.com/docs
- Tailwind CSS IntelliSense (VS Code) for autocompletion and class suggestions: https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss
- Class name helper libraries: clsx or classnames
- Accessibility testing (axe, Lighthouse) - run them early.
Quick checklist before shipping
- Is tailwind.config.js configured (content + theme)?
- Are repeated utility lists extracted into components or @apply classes?
- Do CSS builds remove unused utilities (production optimized)?
- Does the layout follow mobile-first breakpoints?
- Are semantic HTML and accessibility concerns addressed?
- Are dynamic classes expressed in a way Tailwind can detect them?
Conclusion
Tailwind gives you speed and consistency when you embrace its system mindset: configure tokens, extract components, think mobile-first, and never forget semantics. Avoid these common rookie mistakes and you’ll get the real benefits of increased productivity and maintainable UI code.
References
- Tailwind CSS Documentation: https://tailwindcss.com/docs
- Optimizing for production: https://tailwindcss.com/docs/optimizing-for-production
- @apply directive: https://tailwindcss.com/docs/functions-and-directives#apply
- Responsive design: https://tailwindcss.com/docs/responsive-design
- Tailwind CSS IntelliSense (VS Code): https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss