The bugs nobody catches in code review. Each of these compiles, runs, and looks fine in one configuration — then fails the moment something changes.
Dynamic class names
className={`bg-${color}-500`} reads naturally but breaks Tailwind's static scanner. The scanner looks for literal class strings in your source; bg-red-500 as a template literal isn't there, so its CSS isn't generated.
Fix: use a map. const colors = { red: 'bg-red-500', blue: 'bg-blue-500' } as const; className={colors[color]}. The literal classes are in the source, the scanner finds them, the runtime indexing produces the right one.
Arbitrary values everywhere
Tailwind supports bg-[#ff8fbe] for one-off hex values. Convenient. But every arbitrary value bypasses your theme. If you have bg-[#ff8fbe] in three files, you've created three duplicate sources of truth. The brand-color rebrand will miss two.
Fix: if a value appears twice, add it to @theme. Arbitrary values are for genuine one-offs.
Bypassing the cascade with !important
Tailwind ships an ! prefix that adds !important: !text-red-500. Use it for genuinely external CSS that can't be reached otherwise (third-party widget overrides). Don't use it to win against your own components — that means your component CSS is in the wrong layer (lesson 3).
Class strings longer than your component logic
A button with 20 utility classes is harder to read than one with five. When the className gets dense, three options:
- Extract a custom component class in
@layer components(lesson 3). - Group related utilities into named constants:
const baseButton = "inline-flex rounded-lg font-medium". - Use
cn()with structured arrays and conditions (lesson 4).
Don't accept 20-class lines as 'just how Tailwind looks.' That's giving up on readability.
Reaching for CSS-in-JS
If you're tempted to add styled-components or emotion to a Tailwind project, stop. The two systems duplicate each other and add bundle weight. Either go all-Tailwind (utilities + @layer components for the complex cases) or all-CSS-in-JS — not both.