An unofficial command-line linter for Tailwind CSS powered by the Tailwind CSS language tooling.
Note
This is an unofficial project. It is not affiliated with, endorsed by, or sponsored by Tailwind Labs. "Tailwind CSS" is a trademark of Tailwind Labs Inc.
- Detects the problems surfaced by Tailwind CSS IntelliSense, including:
cssConflict,invalidApply,invalidScreen,invalidVariant,invalidConfigPath,invalidTailwindDirective,invalidSourceDirective,recommendedVariantOrder,usedBlocklistedClass,deprecatedAtRule,suggestCanonicalClasses. - Adjust each rule's severity (
ignore/warning/error) via--severity <rule=level>or a config file. - Works with both Tailwind CSS v3 (
tailwind.config.js) and v4 (CSS-based config) projects — the version is auto-detected by the language server. - Lints HTML, JSX/TSX, Vue, Svelte, Astro, PHP and other template languages, as well as CSS files.
- Human-readable text output, machine-readable
--format json, and--format githubfor GitHub Actions annotations. --fix/--fix-dry-runapply the language server's quick-fixes.- CI-friendly exit codes and
--max-warnings.
- Node.js >= 22
- A Tailwind CSS project in the workspace:
- v3: a
tailwind.config.{js,cjs,mjs,ts}andtailwindcssinstalled in the project'snode_modules. - v4: a CSS entrypoint that imports Tailwind (e.g.
@import "tailwindcss";) andtailwindcssinstalled in the project'snode_modules.
- v3: a
The language server loads the project's own tailwindcss (and its config) from
node_modules to compute diagnostics. This means the target project's
dependencies must already be installed before linting. In CI, run your install
step (e.g. npm ci) first:
npm ci
npx tw-lint "src/**/*.{tsx,html}"If the dependencies are not installed, no Tailwind project is detected and the linter reports no problems.
npm install --save-dev @dayflower/tw-linttw-lint [globs...] [options]Examples:
# Lint everything (default globs) in the current project
tw-lint
# Lint specific files
tw-lint "src/**/*.{tsx,html}"
# Machine-readable output
tw-lint "src/**/*.tsx" --format json
# Treat class conflicts as errors
tw-lint --severity cssConflict=error
# Apply fixes
tw-lint "src/**/*.html" --fix
# Apply fixes in a single pass (do not loop)
tw-lint "src/**/*.html" --fix --fix-passes 1When a single class attribute has fixes that overlap (for example a
conflicting-utility removal and a canonical-class rewrite), only the
non-overlapping edits can be applied in one pass. --fix therefore runs up to
--fix-passes passes (default 10, like ESLint's autofix), re-applying fixes
until nothing changes. Set --fix-passes 0 or --fix-passes 1 to keep the
old single-pass behaviour.
Fixed problems disappear from the report once applied (only a count is shown, as
with ESLint). Pass --report-fixed to also list each applied fix under a
Fixed section (and, with --format json, a fixedMessages array per file):
Fixed
src/Button.tsx
4:17 fixed Replace with 'w-xl' suggestCanonicalClasses
4:27 fixed Delete 'p-4' cssConflict
✔ 2 issues fixed
| Option | Description |
|---|---|
--cwd <dir> |
Project root directory (default: current directory). |
--format <text|json|github> |
Output format (default: text). github emits GitHub Actions annotations. |
-c, --config <file> |
Path to a linter config file (see below). |
--severity <rule=level> |
Override a rule severity (ignore/warning/error). Repeatable. Overrides the config file. |
--tailwind-config <file> |
Force a specific Tailwind config file. |
--max-warnings <n> |
Exit non-zero if warnings exceed this number. |
--quiet |
Report errors only. |
--fix |
Apply fixes and write changes to files. |
--fix-dry-run |
Compute fixes without writing changes. |
--fix-passes <n> |
Max fix passes when fixing (default 10; 0 or 1 = single pass). |
--report-fixed |
List the individual fixes applied (off by default). |
--no-error-on-no-project |
Exit 0 instead of 2 when no Tailwind project is detected. |
--verbose |
Print language server logs to stderr. |
Rule severities (and a few other settings) can be persisted in a config file so they don't have to be passed on the command line every time. By default the linter looks in the project root for, in order:
tw-lint.config.json.tw-lintrc.json- a
"tw-lint"key inpackage.json
Use -c, --config <file> to point at a specific file instead.
These rules are the Tailwind CSS IntelliSense lint settings (
tailwindCSS.lint.*). They are not part of yourtailwind.config.js, which only configures Tailwind's design system.
--severity overrides the config file on a per-rule basis, and
--tailwind-config overrides tailwindConfig.
Every value is "ignore", "warning" or "error". The defaults match the
Tailwind CSS IntelliSense extension.
| Rule | Default | Description |
|---|---|---|
invalidScreen |
error |
Unknown screen name in a @screen directive. |
invalidVariant |
error |
Unknown variant (e.g. hvr:underline). |
deprecatedAtRule |
warning |
A deprecated at-rule is used (e.g. @screen in v4). |
invalidTailwindDirective |
error |
Unknown value in a @tailwind directive. |
invalidApply |
error |
A class used in @apply cannot be applied. |
invalidConfigPath |
error |
A theme() / config() path that does not exist. |
cssConflict |
warning |
Two classes on the same element apply the same CSS properties (e.g. p-2 p-4). |
recommendedVariantOrder |
warning |
Stacked variants are not in the recommended order. |
usedBlocklistedClass |
warning |
A class listed in the project's blocklist is used. |
suggestCanonicalClasses |
warning |
Suggests the canonical class for an equivalent one. |
invalidSourceDirective |
error |
Invalid @source directive (Tailwind v4). |
These are defined by the Tailwind CSS language service; the authoritative
descriptions are the tailwindCSS.lint.* settings in the
Tailwind CSS IntelliSense extension settings
(the table above follows the same order). The rule names accepted here are the
keys under tailwindCSS.lint.
0— no errors (and warnings within--max-warnings, if set).1— errors found, or--max-warningsexceeded.2— the linter itself failed, or no Tailwind project was detected for the linted files (nothing was linted). Pass--no-error-on-no-projectto treat the latter as0instead.
tw-lint ships as a reusable GitHub Action. It reports problems as inline
annotations on the run and pull request. As with the CLI, install the target
project's dependencies first so the language server can detect the project.
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci # install the project's own tailwindcss
- uses: dayflower/tw-lint@v1
with:
globs: "src/**/*.{tsx,html}"
max-warnings: "0"Each input maps to the matching CLI option:
| Input | CLI option | Default |
|---|---|---|
globs |
positional globs | (none) |
working-directory |
--cwd |
. |
config |
--config |
(none) |
severity |
--severity (one per line) |
(none) |
tailwind-config |
--tailwind-config |
(none) |
max-warnings |
--max-warnings |
(none) |
quiet |
--quiet |
false |
fix |
--fix |
false |
fix-dry-run |
--fix-dry-run |
false |
fix-passes |
--fix-passes |
10 |
report-fixed |
--report-fixed |
false |
error-on-no-project |
--no-error-on-no-project (inverted) |
true |
verbose |
--verbose |
false |
error-count, warning-count, and fix-count.
The github output format is also available from the CLI directly via
tw-lint --format github if you prefer to run the binary yourself in a workflow.
import { runLint, createTailwindSettings } from '@dayflower/tw-lint'
const summary = await runLint({
cwd: process.cwd(),
patterns: ['src/**/*.tsx'],
settings: createTailwindSettings({ rules: { cssConflict: 'error' } }),
})
console.log(summary.errorCount, summary.warningCount)Instead of re-implementing lint rules, this tool drives
@tailwindcss/language-server
as a headless LSP client.
The server performs project detection (Tailwind v3 and v4), config loading
and validation using
@tailwindcss/language-service
internally — the very same engine behind the Tailwind CSS IntelliSense editor
extension. This tool collects the resulting diagnostics, reports them on the
command line, and can apply the server's quick-fixes.
- Spawns
tailwindcss-language-server --stdioand connects over JSON-RPC. - Initializes the workspace and responds to the server's
workspace/configurationrequests with the lint settings. - Opens each matched document to trigger project initialization, waits for the project to initialize, then forces a fresh validation per document and collects the published diagnostics.
- For
--fix, requeststextDocument/codeActionquick-fixes and applies the returned edits.
{ // Rule severities: "ignore" | "warning" | "error" "rules": { "cssConflict": "error", "recommendedVariantOrder": "ignore" }, // Attributes/functions whose string values are scanned for classes "classAttributes": ["class", "className", "tw"], "classFunctions": ["clsx", "cva", "cn"], // Map extra language ids to a known one (e.g. for custom templates) "includeLanguages": { "plaintext": "html" }, // Equivalent to --tailwind-config "tailwindConfig": "./tailwind.config.ts", // Equivalent to --fix-passes (max fix passes; 0 or 1 = single pass) "fixPasses": 10, // Equivalent to --report-fixed (list the fixes applied) "reportFixed": false }