|
| 1 | +# tailwind-impl-config-v3 : Anti-Patterns |
| 2 | + |
| 3 | +Common `tailwind.config.js` mistakes with WHY they fail and the fix. |
| 4 | + |
| 5 | +## AP-1 : `theme: { colors: {...} }` Without `extend` |
| 6 | + |
| 7 | +**Symptom** : `bg-red-500`, `text-slate-900`, every default colour utility stops working. |
| 8 | + |
| 9 | +```js |
| 10 | +/* WRONG */ |
| 11 | +theme: { |
| 12 | + colors: { brand: "#3b82f6" }, |
| 13 | +} |
| 14 | +``` |
| 15 | + |
| 16 | +**Why** : `theme.<namespace>` REPLACES the entire default namespace. The default colour palette is wiped. |
| 17 | + |
| 18 | +**Fix** : |
| 19 | + |
| 20 | +```js |
| 21 | +theme: { |
| 22 | + extend: { |
| 23 | + colors: { brand: "#3b82f6" }, // keeps defaults, adds brand |
| 24 | + }, |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +## AP-2 : Content Glob Leaking Into `node_modules` |
| 29 | + |
| 30 | +**Symptom** : Build takes minutes. Editor lags. Out-of-memory errors. |
| 31 | + |
| 32 | +```js |
| 33 | +/* WRONG */ |
| 34 | +content: ["./**/*.{js,html}"] |
| 35 | +``` |
| 36 | + |
| 37 | +**Why** : Glob walks every nested directory including `node_modules`, scanning thousands of files. |
| 38 | + |
| 39 | +**Fix** : |
| 40 | + |
| 41 | +```js |
| 42 | +/* RIGHT */ |
| 43 | +content: ["./src/**/*.{js,ts,jsx,tsx,html}", "./index.html"] |
| 44 | +``` |
| 45 | + |
| 46 | +ALWAYS root the glob at a specific dir (`./src`, `./app`, `./pages`). NEVER use `./**/*` at the repo root. |
| 47 | + |
| 48 | +## AP-3 : `darkMode: 'class'` Without Class on `<html>` |
| 49 | + |
| 50 | +**Symptom** : Adding `dark:bg-black` to a button does nothing. The dark variant compiles but never matches. |
| 51 | + |
| 52 | +```js |
| 53 | +/* config */ |
| 54 | +darkMode: 'class', |
| 55 | +``` |
| 56 | + |
| 57 | +```html |
| 58 | +<!-- WRONG : no class anywhere --> |
| 59 | +<html> |
| 60 | + <body> |
| 61 | + <button class="bg-white dark:bg-black">Hi</button> |
| 62 | + </body> |
| 63 | +</html> |
| 64 | +``` |
| 65 | + |
| 66 | +**Why** : `darkMode: 'class'` requires a `.dark` ancestor for the `dark:` variant to fire. Without it the variant never matches. |
| 67 | + |
| 68 | +**Fix** : |
| 69 | + |
| 70 | +```html |
| 71 | +<html class="dark"> |
| 72 | + ... |
| 73 | +</html> |
| 74 | +``` |
| 75 | + |
| 76 | +Or a runtime toggle via JS. |
| 77 | + |
| 78 | +## AP-4 : Next.js Catch-All Glob With Literal Brackets |
| 79 | + |
| 80 | +**Symptom** : Classes inside `app/[...slug]/page.tsx` never get picked up. |
| 81 | + |
| 82 | +```js |
| 83 | +/* WRONG */ |
| 84 | +content: ["./app/[...slug]/**/*.{js,ts,jsx,tsx}"] |
| 85 | +``` |
| 86 | + |
| 87 | +**Why** : The glob engine treats `[...slug]` as a character class (a set of characters), not a literal directory name. |
| 88 | + |
| 89 | +**Fix** : |
| 90 | + |
| 91 | +```js |
| 92 | +/* RIGHT : let the broader glob match the dynamic route */ |
| 93 | +content: ["./app/**/*.{js,ts,jsx,tsx}"] |
| 94 | +``` |
| 95 | + |
| 96 | +Or escape the brackets explicitly if you must scope precisely : |
| 97 | + |
| 98 | +```js |
| 99 | +content: ["./app/\\[...slug\\]/**/*.{js,ts,jsx,tsx}"] |
| 100 | +``` |
| 101 | + |
| 102 | +## AP-5 : Mixing CommonJS and ESM in Same Config |
| 103 | + |
| 104 | +**Symptom** : Node throws on load OR the config is silently ignored. |
| 105 | + |
| 106 | +```js |
| 107 | +/* WRONG */ |
| 108 | +import typography from "@tailwindcss/typography"; |
| 109 | + |
| 110 | +module.exports = { |
| 111 | + plugins: [typography], |
| 112 | +}; |
| 113 | +``` |
| 114 | + |
| 115 | +**Why** : `import` is ESM, `module.exports` is CJS. Node refuses to mix them in the same file. |
| 116 | + |
| 117 | +**Fix** : Pick one and stick to it. |
| 118 | + |
| 119 | +```js |
| 120 | +/* RIGHT : CJS */ |
| 121 | +const typography = require("@tailwindcss/typography"); |
| 122 | + |
| 123 | +module.exports = { |
| 124 | + plugins: [typography], |
| 125 | +}; |
| 126 | +``` |
| 127 | + |
| 128 | +```ts |
| 129 | +/* RIGHT : ESM (file extension .mjs OR package "type": "module") */ |
| 130 | +import type { Config } from "tailwindcss"; |
| 131 | +import typography from "@tailwindcss/typography"; |
| 132 | + |
| 133 | +export default { plugins: [typography] } satisfies Config; |
| 134 | +``` |
| 135 | + |
| 136 | +## AP-6 : `safelist` Used With v4 Migration Coming |
| 137 | + |
| 138 | +**Symptom** : v4 build silently drops safelisted classes ; production CSS is missing them. |
| 139 | + |
| 140 | +```js |
| 141 | +/* OK in v3 but BREAKS on v4 migration */ |
| 142 | +safelist: ["bg-red-500", { pattern: /bg-(red|blue)-(100|500)/ }], |
| 143 | +``` |
| 144 | + |
| 145 | +**Why** : `safelist` is REMOVED in v4. Its config-time evaluation has no CSS-side equivalent. |
| 146 | + |
| 147 | +**Fix for v4 migration** : Move to `@source inline(...)` in CSS : |
| 148 | + |
| 149 | +```css |
| 150 | +/* main.css */ |
| 151 | +@import "tailwindcss"; |
| 152 | +@source inline("bg-red-500"); |
| 153 | +@source inline("bg-{red,blue}-{100,500}"); |
| 154 | +``` |
| 155 | + |
| 156 | +Plan the migration in advance : delete `safelist` from v3 config in the same commit that adds `@source inline` to CSS. |
| 157 | + |
| 158 | +## AP-7 : Disabling Core Plugins Before v4 Migration |
| 159 | + |
| 160 | +```js |
| 161 | +/* OK in v3 but DEAD on v4 migration */ |
| 162 | +corePlugins: { float: false } |
| 163 | +``` |
| 164 | + |
| 165 | +**Why** : `corePlugins` is REMOVED in v4. There is NO replacement. v4 cannot disable specific utility families. |
| 166 | + |
| 167 | +**Fix** : |
| 168 | + |
| 169 | +1. Audit which utilities you're disabling and WHY. |
| 170 | +2. Replace the rationale (lint rule, stylelint, CI check) with a non-Tailwind enforcement mechanism. |
| 171 | +3. Remove `corePlugins` before migrating to v4. |
| 172 | + |
| 173 | +## AP-8 : `separator: '_'` for "Cleaner" Class Names |
| 174 | + |
| 175 | +**Symptom** : Project works in v3, breaks on v4 migration ; all `_` separators silently revert to `:`. |
| 176 | + |
| 177 | +```js |
| 178 | +/* WRONG (and gone in v4) */ |
| 179 | +separator: "_", |
| 180 | +``` |
| 181 | + |
| 182 | +**Why** : `separator` is REMOVED in v4. v4 fixes the separator at `:`. |
| 183 | + |
| 184 | +**Fix** : Use the default `:`. Refactor any tooling that expects `_`. |
| 185 | + |
| 186 | +## AP-9 : Using `theme()` Inside Arbitrary Values at Runtime |
| 187 | + |
| 188 | +**Symptom** : `style={{ padding: theme('spacing.4') }}` throws at runtime because `theme()` is a Tailwind config-time function, not a JS runtime API. |
| 189 | + |
| 190 | +**Why** : `theme()` only works inside the Tailwind config file (passed to plugins). At runtime it does not exist. |
| 191 | + |
| 192 | +**Fix** : Use CSS variables OR resolved values : |
| 193 | + |
| 194 | +```js |
| 195 | +// Pre-resolve at build : import from config or use resolveConfig() |
| 196 | +import resolveConfig from "tailwindcss/resolveConfig"; |
| 197 | +import tailwindConfig from "../../tailwind.config.js"; |
| 198 | + |
| 199 | +const fullConfig = resolveConfig(tailwindConfig); |
| 200 | +const spacing4 = fullConfig.theme.spacing[4]; |
| 201 | +``` |
| 202 | + |
| 203 | +Or read CSS variables emitted by Tailwind into root. |
| 204 | + |
| 205 | +## AP-10 : Forgetting `relative: true` for Monorepo Subdirs |
| 206 | + |
| 207 | +**Symptom** : Classes in `../packages/ui` are scanned but referenced paths in built CSS are wrong (resolve to `node_modules` instead of source). |
| 208 | + |
| 209 | +```js |
| 210 | +/* sometimes WRONG in monorepos */ |
| 211 | +content: ["../packages/ui/src/**/*.tsx"] |
| 212 | +``` |
| 213 | + |
| 214 | +**Fix** : |
| 215 | + |
| 216 | +```js |
| 217 | +content: { |
| 218 | + relative: true, |
| 219 | + files: ["../packages/ui/src/**/*.tsx"], |
| 220 | +} |
| 221 | +``` |
| 222 | + |
| 223 | +`relative: true` makes globs resolve against the config file location, not CWD. |
| 224 | + |
| 225 | +## AP-11 : Using `prefix: 'tw-'` Without Coordinating Editor Tools |
| 226 | + |
| 227 | +**Symptom** : IntelliSense breaks (extension expects unprefixed classes), prettier-plugin-tailwindcss does not sort, eslint-plugin-tailwindcss flags every utility. |
| 228 | + |
| 229 | +**Fix** : Configure every editor tool to honour the prefix : |
| 230 | + |
| 231 | +```json |
| 232 | +{ |
| 233 | + "tailwindCSS.classAttributes": ["class", "className", "ngClass"], |
| 234 | + "tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]], |
| 235 | + "tailwindCSS.includeLanguages": { "plaintext": "html" } |
| 236 | +} |
| 237 | +``` |
| 238 | + |
| 239 | +For prettier : `prefix: "tw-"` is auto-detected from `tailwind.config.js` if `prettier-plugin-tailwindcss` is installed. |
| 240 | + |
| 241 | +## Verified Sources |
| 242 | + |
| 243 | +- https://v3.tailwindcss.com/docs/content-configuration |
| 244 | +- https://v3.tailwindcss.com/docs/configuration |
| 245 | +- https://v3.tailwindcss.com/docs/dark-mode |
| 246 | +- https://github.com/tailwindlabs/tailwindcss/issues/18136 (dynamic class names) |
| 247 | + |
| 248 | +Last verified : 2026-05-19. |
0 commit comments