Use when configuring a Tailwind CSS v3.4 project: writing the `tailwind.config.js` / `.mjs` / `.ts` file, setting up content scanning globs, extending or replacing the default theme, registering plugins, picking a dark-mode strategy, disabling core utilities, safelisting classes that string-concatenation produces, or applying a project-wide prefix. Use also when reading an existing v3 config and needing to know the precise semantics of `theme` vs `theme.extend`, what `corePlugins`, `safelist`, and `separator` do, and which options are REMOVED in v4 so a v3 to v4 migration plan can flag them in advance. Prevents (a) accidentally REPLACING the default theme by writing `theme: { colors: {...} }` when `theme.extend.colors` was intended, (b) leaking content scanning into `node_modules` via `./**/*.{js,html}` and tanking build times, (c) configuring `darkMode: 'class'` and forgetting to toggle the class on `<html>` (no dark styles ever fire), (d) using `corePlugins: { float: false }`, `safelist: [...]`, or `separat
Use when configuring a Tailwind CSS v3.4 project: writing the `tailwind.config.js` / `.mjs` / `.ts` file, setting up content scanning globs, extending or replacing the default theme, registering plugins, picking a dark-mode strategy, disabling core utilities, safelisting classes that string-concatenation produces, or applying a project-wide prefix. Use also when reading an existing v3 config and needing to know the precise semantics of `theme` vs `theme.extend`, what `corePlugins`, `safelist`, and `separator` do, and which options are REMOVED in v4 so a v3 to v4 migration plan can flag them in advance. Prevents (a) accidentally REPLACING the default theme by writing `theme: { colors: {...} }` when `theme.extend.colors` was intended, (b) leaking content scanning into `node_modules` via `./**/*.{js,html}` and tanking build times, (c) configuring `darkMode: 'class'` and forgetting to toggle the class on `<html>` (no dark styles ever fire), (d) using `corePlugins: { float: false }`, `safelist: [...]`, or `separator: '_'` in a v3 project that is about to migrate to v4 (all three are silently removed), (e) building Next.js catch-all routes `[...slug]` into the content glob with literal brackets that the glob engine treats as a character class (none of the files inside match), and (f) mixing CommonJS and ESM exports in the same config (`module.exports` vs `export default`). Covers the complete v3 configuration surface: file location and module-system variants (`tailwind.config.js` CJS, `tailwind.config.mjs` ESM, `tailwind.config.ts` TypeScript-typed), content scanning with glob patterns, monorepo paths, `{ relative: true, files: [...] }` and the `transform` and `extract` advanced options, `theme` (REPLACE everything) vs `theme.extend` (ADDITIVE) semantics across every namespace, `presets: []` for shared base configs, the `plugin()` factory with all eight helpers (`addUtilities`, `matchUtilities`, `addComponents`, `matchComponents`, `addBase`, `addVariant`, `matchVariant`, `theme`, `config`, `e`), `darkMode` strategies (`media`, `class`, selector array, fully custom variant), `corePlugins: { float: false }` and `corePlugins: ['preflight']` to disable individual or whole-core utilities, `safelist: ['bg-red-500', { pattern: /bg-(red|blue)-(100|200)/, variants: ['hover'] }]`, `blocklist: ['container']`, `prefix: 'tw-'`, `important: true | '#app'`, `separator: '_'`, and `future: { hoverOnlyWhenSupported: true }`. Explicitly flags every option that is REMOVED OR CHANGED in v4 so migration planning is unambiguous. Keywords: tailwind config js, tailwind v3 configuration, content glob, theme extend, presets, plugins array, darkMode class, darkMode media, darkMode selector, corePlugins disable float, safelist pattern, blocklist, prefix tw-, important selector, separator, future flags, hoverOnlyWhenSupported, why is my tailwind class not generating, content array not picking up files, Next-js catch-all glob, monorepo tailwind, addUtilities, matchUtilities, addVariant, plugin helpers, removed in v4, what changes in v4, ESM CJS tailwind config, TypeScript tailwind config, tailwind 3-4.
license
MIT
compatibility
Designed for Claude Code. Requires Tailwind CSS v3.4.
In v3.4 the entire Tailwind project is configured through a single JavaScript file (tailwind.config.js / .mjs / .ts). Every behaviour, every token, every plugin lives here. Mastering this file is mastering v3. NOTE: v4 moves most of this into CSS @theme { ... } blocks ; this skill is v3-ONLY and explicitly flags every option that is REMOVED in v4.
Quick Reference
Option matrix (v3.4 full surface)
Option : purpose : default : v4 status
content : files to scan for class names : [] : moved to @source "./path" directives
theme : REPLACE all default tokens : {} (extend defaults) : moved to @theme block
theme.extend : ADD to default tokens : {} : moved to @theme (always additive)
presets : shared base configs : [] : @import chains in CSS
plugins : JS plugins (addUtilities etc.) : [] : still supported via @plugin "./..."
darkMode : dark-mode strategy : 'media' : @custom-variant dark (...) in CSS
corePlugins : disable built-ins : enabled : REMOVED in v4 (no replacement)
prefix: 'tw' in v4 produces tw:flex (variant-style), NOT tw-flex.
important: true
@important directive OR trailing ! per utility.
Decision Trees
Picking the config-file format
TypeScript-first project?
├── ALWAYS use `tailwind.config.ts` with `import type { Config } from 'tailwindcss'`
├── exports the typed config and `module.exports = config` (or `export default config` if ESM)
ESM-only project (`"type": "module"` in package.json)?
├── ALWAYS use `tailwind.config.mjs` (or `.ts`) with `export default { ... }`
├── NEVER mix CommonJS (`module.exports`) with ESM (`export default`) in the same file
Plain JS project?
├── ALWAYS use `tailwind.config.js` with `module.exports = { ... }` (CJS)
└── Add the JSDoc type comment `/** @type {import('tailwindcss').Config} */` for editor IntelliSense
theme vs theme.extend
adding NEW values to defaults?
└── ALWAYS use `theme.extend.{namespace}.{key} = value` (additive)
REPLACING the entire default namespace (e.g. wiping the default colors)?
└── ALWAYS use `theme.{namespace} = { onlyMyValues }` (NO `.extend`)
└── DANGER : you lose every default color. `bg-red-500`, `text-slate-900`,
every default theme reference STOPS WORKING. Confirm before committing.
partial replacement (keep some defaults, override others)?
└── Use `theme.extend.{namespace}` with the same key as a default to override it
└── e.g. `theme.extend.colors.red = { 500: '#custom-red' }` overrides ONLY red-500
corePlugins mental model (v3-only ; gone in v4)
want to disable specific utilities?
├── few specific utilities : `corePlugins: { float: false, clear: false }`
├── most utilities : ALLOWLIST mode `corePlugins: ['preflight', 'container']`
│ └── lists only the utilities to KEEP ; everything else is disabled
└── note : v4 has NO equivalent. If migration to v4 is on the roadmap, document
the disabled utilities as a project rule and remove the `corePlugins` block
before upgrading.
safelist mental model (v3-only ; gone in v4)
class name lands in DOM but no CSS is generated?
├── string-concatenation source (`bg-${color}`) ?
│ └── ROOT CAUSE : content scan needs literal token. Fix at source (preferred)
│ OR add to safelist (escape hatch).
├── CMS / server-side generated class names not in any source file ?
│ └── safelist : ['text-3xl', 'bg-red-500', ...]
└── pattern-based force-include ?
└── safelist : [{ pattern: /bg-(red|blue|green)-(100|200|300|400|500)/, variants: ['hover', 'focus'] }]
darkMode strategy
which dark-mode trigger?
├── follow OS preference (no opt-in) : `darkMode: 'media'` (default ; matches `prefers-color-scheme: dark`)
├── manual `.dark` class on `<html>` : `darkMode: 'class'`
├── custom attribute `data-theme="dark"` : `darkMode: ['class', '[data-theme="dark"]']` (v3.4.1+)
├── manual selector beyond `.dark` (e.g. `.dark` OR `[data-theme=dark]`) : ['selector', '[data-theme=dark]']
└── fully custom expression : `darkMode: ['variant', '&:not(.light *)']`
Patterns
Pattern 1: Content scanning (the most consequential block)
module.exports = {
content: [
'./index.html',
'./src/**/*.{js,ts,jsx,tsx,vue,svelte,mdx}',
'./node_modules/@my-org/ui-lib/dist/**/*.{js,mjs}', // include a published shared library
],
// ...
}
Critical rules :
ALWAYS list exact file extensions. *.{js} skips .jsx ; *.{js,ts,jsx,tsx} covers all.
NEVER use ./**/*.{js,html} from the project root : it scans node_modules and tanks build time.
For Next.js / Remix projects with catch-all routes [...slug], escape the brackets to prevent the glob engine from interpreting them as a character class :
content: [
'./pages/[[]...slug[]]/*.{js,jsx,tsx}', // literal brackets via [[ ]]
]
For monorepos with workspaces, point at each workspace explicitly :
relative: true ALWAYS makes paths resolve relative to the config file, not the CWD. Use this in monorepos where the build runs from the root but the config lives in a workspace.
Pattern 3: theme.extend for additive customisations