Overview

Tailwind’s class-string model is writable but not readable without tooling. The Prettier plugin, the ESLint plugin, and the VS Code extension make class order deterministic, flag mistakes statically, and provide autocomplete for every utility and variant. The parent reference is tailwind.

Install prettier-plugin-tailwindcss first

The Prettier plugin sorts utility classes in a canonical order on every save. Install it before writing a single line of Tailwind so the codebase has consistent order from the start.

npm install -D prettier prettier-plugin-tailwindcss
// .prettierrc
{
  "plugins": ["prettier-plugin-tailwindcss"]
}

The canonical order groups by category: layout, positioning, spacing, sizing, typography, color, effects. Diffs become readable: a change to spacing shows in the spacing section of the class list, not scattered through a random-order string. Teams stop debating class order in review.

Install eslint-plugin-tailwindcss for static analysis

The ESLint plugin flags class-name conflicts, unknown classes, and shorthand opportunities before they reach the browser.

npm install -D eslint-plugin-tailwindcss
// eslint.config.json (flat config)
{
  "plugins": { "tailwindcss": require("eslint-plugin-tailwindcss") },
  "rules": {
    "tailwindcss/classnames-order": "warn",
    "tailwindcss/no-contradicting-classname": "error",
    "tailwindcss/no-unnecessary-arbitrary-value": "warn"
  }
}

no-contradicting-classname catches the most damaging class of bug: two utilities in the same property group applied to the same element, where only the last one in Tailwind’s generated output wins. For example, flex-col flex-row on one element silently applies only one; the linter makes this a visible error.

no-unnecessary-arbitrary-value flags cases where an arbitrary value matches a built-in token. It is the automated version of the extract-after-third-use rule from tailwind-arbitrary-values.

Use the VS Code extension for IntelliSense

The bradlc.vscode-tailwind-css extension provides:

  • Autocomplete for all utilities, variants, and tokens from your @theme.
  • Hover previews showing the generated CSS for any class.
  • Color swatches next to color utilities.
  • Linting inside template strings in JavaScript and TypeScript.

Configure it to recognize class name strings in non-standard attributes or component props:

// .vscode/settings.json
{
  "tailwindCSS.classAttributes": ["class", "className", "classList", "ngClass", "styles"]
}

Diagnose class conflicts with browser DevTools

When a utility appears to do nothing, open DevTools and check the computed styles panel.

  • If the utility’s property is listed with a strikethrough, another rule is winning. Check whether the winning rule is in a higher cascade layer or has higher specificity; see css-cascade-layers.
  • If the utility is not listed at all, the class is not being scanned into the build. This happens with dynamically constructed class strings; Tailwind’s scanner matches literal strings, not concatenated ones.
// Broken: the scanner cannot see these classes at build time
const cls = "bg-" + color;
 
// Fixed: use a complete class string; let JavaScript select between them
const cls = color === "brand" ? "bg-brand" : "bg-neutral-100";

Fix dynamically constructed class strings

Tailwind scans source files for class strings at build time using a literal string match. Concatenated or computed class names are invisible to the scanner.

Patterns that break scanning:

`text-${size}-600`      // broken
"bg-" + colorName       // broken
classNames({ [cls]: true }) // broken if cls is a variable

Patterns that work:

size === "lg" ? "text-lg-600" : "text-base-600"  // full strings
{ "bg-brand": isActive, "bg-neutral-100": !isActive }  // full class names as object keys

For libraries that assemble class names from fragments, add a safelist of complete class names to your CSS file:

@source "./generated-classes.txt";

Common build errors and their causes

  • Utility not applying despite appearing in source. The class is in a string that the scanner cannot parse. Use complete literal strings.
  • Tokens not generating utilities. The @theme block is in a file that is not imported as the Tailwind entry point. Only the file you pass to the Vite plugin or CLI generates utilities from its @theme.
  • Styles not updating in dev. The Vite plugin’s HMR missed a file. Restart the dev server and check that the entry CSS file is imported in your main JavaScript entry.
  • Specificity override not working. Your custom CSS is inside a @layer that Tailwind’s utilities layer wins over. Move the override to a later layer or use an unlayered rule.