|
| 1 | +--- |
| 2 | +name: tailwind-agents-validator |
| 3 | +description: > |
| 4 | + Use when reviewing a Tailwind CSS codebase for cross-rule violations, |
| 5 | + performing a pre-PR audit of changes that touch styling, validating |
| 6 | + a v3-to-v4 migration result, or running a periodic codebase health |
| 7 | + check across utility classes, plugin authoring, and configuration |
| 8 | + files. The agent acts as a Tailwind-specific code reviewer that |
| 9 | + enforces every rule documented in the sibling skills of this |
| 10 | + package, producing an actionable report that cites the authoritative |
| 11 | + sibling skill for each finding. Prevents the four highest-impact |
| 12 | + Tailwind code-review misses: dynamic template-literal class strings |
| 13 | + that compile cleanly but emit no CSS in production, scoped @apply |
| 14 | + blocks in Vue/Svelte/CSS-modules without @reference under v4, |
| 15 | + v3-only config keys (corePlugins, safelist, separator) silently |
| 16 | + ignored after a v4 upgrade, and bare border/ring/placeholder usage |
| 17 | + inheriting the wrong default colour post-migration. Covers the full |
| 18 | + rule catalogue (no dynamic class strings, no scoped @apply without |
| 19 | + @reference, no v3 keys in v4 config, no undefined custom classes |
| 20 | + outside @layer, variant-stack order consistency, explicit border |
| 21 | + and ring colours, no em-dash in user-facing strings, deterministic |
| 22 | + prop-to-class mapping, twMerge ordering correctness, plugin theme() |
| 23 | + default-shipping), the report format (severity, file:line, rule |
| 24 | + citation, sibling-skill link, fix suggestion), the recursive scan |
| 25 | + pattern (TSX, JSX, Vue, Svelte, CSS, MDX), the v3-or-v4 detection |
| 26 | + heuristic, and integration into CI as a non-blocking advisory. |
| 27 | + Keywords: tailwind code review, tailwind validator, tailwind audit, |
| 28 | + tailwind linter, pre-PR check tailwind, post-migration audit, v3 v4 |
| 29 | + consistency check, cross-rule check tailwind, dynamic class |
| 30 | + detection, scoped @apply check, @reference missing, corePlugins |
| 31 | + leftover, safelist leftover, separator leftover, bare border bare |
| 32 | + ring, prop-to-class consistency, variant order check, twMerge |
| 33 | + audit, my codebase upgrade looks clean but, find all dynamic |
| 34 | + classes, find all scoped @apply, find tailwind antipatterns, |
| 35 | + tailwind health check, before I ship audit. |
| 36 | +license: MIT |
| 37 | +compatibility: "Designed for Claude Code. Requires Tailwind CSS v3.4 or v4.0+." |
| 38 | +metadata: |
| 39 | + author: OpenAEC-Foundation |
| 40 | + version: "1.0" |
| 41 | +--- |
| 42 | + |
| 43 | +# Tailwind Validator : Cross-Skill Consistency Agent |
| 44 | + |
| 45 | +Acts as a Tailwind-specific code reviewer. Reads the codebase, runs |
| 46 | +every rule documented in the sibling skills, produces an actionable |
| 47 | +report that cites the authoritative source per finding. |
| 48 | + |
| 49 | +The validator does NOT modify code. It reports. The developer (or a |
| 50 | +follow-up agent) applies fixes guided by the cited sibling skill. |
| 51 | + |
| 52 | +## How To Invoke |
| 53 | + |
| 54 | +The user asks "validate my Tailwind code" or "audit this codebase for |
| 55 | +Tailwind issues" or "run a pre-PR check on the styling changes". The |
| 56 | +agent : |
| 57 | + |
| 58 | +1. Detects v3 vs v4 (heuristic in Method 1, `references/methods.md`). |
| 59 | +2. Runs each rule in the catalogue (Section : Rule Catalogue). |
| 60 | +3. Emits the report in the standard format (Section : Report Format). |
| 61 | + |
| 62 | +## Detect : v3 or v4 |
| 63 | + |
| 64 | +Decision sources, in priority order : |
| 65 | + |
| 66 | +1. `package.json` `dependencies.tailwindcss` semver. `^3.x` is v3. |
| 67 | + `^4.x` is v4. |
| 68 | +2. CSS entry file contains `@import "tailwindcss"` (v4) OR three |
| 69 | + `@tailwind` directives (v3). |
| 70 | +3. PostCSS config plugin is `@tailwindcss/postcss` (v4) OR |
| 71 | + `tailwindcss` (v3). |
| 72 | +4. Vite config imports `@tailwindcss/vite` (v4) OR uses the PostCSS |
| 73 | + plugin (v3). |
| 74 | + |
| 75 | +If signals conflict, report a Severity-Error mismatch FIRST. Mixed |
| 76 | +configurations produce silent failures. |
| 77 | + |
| 78 | +Mixed-state cross-link : `tailwind-impl-migration-v3-v4`. |
| 79 | + |
| 80 | +## Rule Catalogue |
| 81 | + |
| 82 | +Each rule names a check, the file types it scans, the sibling skill |
| 83 | +that documents the rule, and the severity. |
| 84 | + |
| 85 | +### Rule R-01 : No Dynamic Template-Literal Class Strings |
| 86 | + |
| 87 | +**Scans** : `.tsx`, `.jsx`, `.vue`, `.svelte`, `.astro`, `.mdx` |
| 88 | + |
| 89 | +**Detect** : grep for `` className=`...${...}...` `` and `class="...{{ ...?... }}...{{ ... }}"` patterns that build a class fragment from a variable. |
| 90 | + |
| 91 | +**Severity** : Error (production bug, classes silently missing) |
| 92 | + |
| 93 | +**Sibling skill** : `tailwind-errors-dynamic-classes` |
| 94 | + |
| 95 | +**Fix** : static map per Method 1 of the sibling skill. |
| 96 | + |
| 97 | +### Rule R-02 : Scoped @apply Without @reference (v4 only) |
| 98 | + |
| 99 | +**Scans** : `.vue`, `.svelte`, `*.module.css` |
| 100 | + |
| 101 | +**Detect** : file contains `@apply` inside a scoped `<style>` block or |
| 102 | +a CSS module, and the same block does NOT contain `@reference`. |
| 103 | + |
| 104 | +**Severity** : Error (build fails with "Cannot apply unknown utility |
| 105 | +class") |
| 106 | + |
| 107 | +**Sibling skill** : `tailwind-impl-apply-directive` |
| 108 | + |
| 109 | +**Fix** : prepend `@reference "../path/to/app.css";` inside the style |
| 110 | +block. |
| 111 | + |
| 112 | +### Rule R-03 : Forbidden v3 Config Keys in v4 Project |
| 113 | + |
| 114 | +**Scans** : `tailwind.config.js`, `tailwind.config.ts` |
| 115 | + |
| 116 | +**Detect** : project is v4 AND config file contains any of |
| 117 | +`corePlugins`, `safelist`, `separator`. |
| 118 | + |
| 119 | +**Severity** : Warning (silently ignored, original intent lost) |
| 120 | + |
| 121 | +**Sibling skill** : `tailwind-errors-v4-migration`, fallback to |
| 122 | +`tailwind-impl-migration-v3-v4` |
| 123 | + |
| 124 | +**Fix** : |
| 125 | +- `corePlugins` : remove and replace with ESLint rule or `@source not` |
| 126 | +- `safelist` : convert to `@source inline("...")` in CSS |
| 127 | +- `separator` : remove ; v4 fixes separator to `:` |
| 128 | + |
| 129 | +### Rule R-04 : Undefined Custom Class Outside @layer |
| 130 | + |
| 131 | +**Scans** : `.css` |
| 132 | + |
| 133 | +**Detect** : project is v3 and file contains a custom class |
| 134 | +declaration `(.classname { ... })` outside `@layer base` / `@layer |
| 135 | +components` / `@layer utilities`. Project is v4 and file contains a |
| 136 | +custom class without `@utility` or `@layer`. |
| 137 | + |
| 138 | +**Severity** : Warning (cascade ordering unpredictable, overrides |
| 139 | +behave inconsistently) |
| 140 | + |
| 141 | +**Sibling skill** : `tailwind-impl-config-v3`, `tailwind-impl-config-v4` |
| 142 | + |
| 143 | +**Fix** : wrap in `@layer components` (v3) or rewrite as `@utility` |
| 144 | +(v4). |
| 145 | + |
| 146 | +### Rule R-05 : Variant Stack Order |
| 147 | + |
| 148 | +**Scans** : `.tsx`, `.jsx`, `.vue`, `.svelte`, `.html` |
| 149 | + |
| 150 | +**Detect** : v4 project contains a class with two or more variants |
| 151 | +that look v3-style. Common patterns : `first:*:`, `last:*:`, |
| 152 | +`hover:lg:` (the lg should come first under v4 left-to-right rule for |
| 153 | +breakpoints AT the variant level). |
| 154 | + |
| 155 | +**Severity** : Warning (visual regression after v3 to v4) |
| 156 | + |
| 157 | +**Sibling skill** : `tailwind-syntax-variants`, |
| 158 | +`tailwind-impl-migration-v3-v4` |
| 159 | + |
| 160 | +**Fix** : flip the order. `first:*:pt-0` becomes `*:first:pt-0`. |
| 161 | + |
| 162 | +### Rule R-06 : Bare border / ring / placeholder Under v4 |
| 163 | + |
| 164 | +**Scans** : `.tsx`, `.jsx`, `.vue`, `.svelte`, `.html` |
| 165 | + |
| 166 | +**Detect** : v4 project AND class list contains a bare `border`, |
| 167 | +`ring`, `placeholder` token with no following colour or width |
| 168 | +modifier on the same element. |
| 169 | + |
| 170 | +**Severity** : Info (visual regression risk : default colour changed |
| 171 | +from `gray-200` / `blue-500` / `gray-400` to `currentColor`) |
| 172 | + |
| 173 | +**Sibling skill** : `tailwind-errors-v4-migration`, |
| 174 | +`tailwind-impl-migration-v3-v4` |
| 175 | + |
| 176 | +**Fix** : add explicit colour (`border-zinc-200`, `ring-blue-500`, |
| 177 | +`placeholder-zinc-400`) OR restore v3 defaults globally via `@layer |
| 178 | +base` (see method). |
| 179 | + |
| 180 | +### Rule R-07 : Em-Dash in User-Facing String |
| 181 | + |
| 182 | +**Scans** : `.tsx`, `.jsx`, `.vue`, `.svelte`, `.html`, `.mdx` |
| 183 | + |
| 184 | +**Detect** : literal `—` (U+2014) inside JSX text, attribute strings |
| 185 | +that render to users (`title`, `aria-label`, `placeholder`), or |
| 186 | +component children. |
| 187 | + |
| 188 | +**Severity** : Info (typography standard ; CLAUDE.md project rule) |
| 189 | + |
| 190 | +**Sibling skill** : (project rule, see workspace CLAUDE.md) |
| 191 | + |
| 192 | +**Fix** : replace with `.`, `,`, or rewrite the sentence. NEVER |
| 193 | +replace with `-`. |
| 194 | + |
| 195 | +### Rule R-08 : Deterministic Prop-to-Class Mapping |
| 196 | + |
| 197 | +**Scans** : `.tsx`, `.jsx`, `.vue`, `.svelte` |
| 198 | + |
| 199 | +**Detect** : component receives a `variant` / `color` / `size` prop |
| 200 | +and constructs the class via a switch statement, ternary chain, or |
| 201 | +function call that returns differently-shaped strings per branch. |
| 202 | + |
| 203 | +**Severity** : Info (maintainability ; not a bug, but harder to |
| 204 | +audit) |
| 205 | + |
| 206 | +**Sibling skill** : `tailwind-core-design-system` |
| 207 | + |
| 208 | +**Fix** : refactor to a single object literal mapping prop value to |
| 209 | +complete class string. |
| 210 | + |
| 211 | +### Rule R-09 : twMerge Order Mistake |
| 212 | + |
| 213 | +**Scans** : `.tsx`, `.jsx`, `.vue`, `.svelte` |
| 214 | + |
| 215 | +**Detect** : `twMerge(...)` or `cn(...)` call with conflicting |
| 216 | +utilities where the LATER argument is the loser (e.g. |
| 217 | +`twMerge('p-4', 'p-2')` resolves to `p-2` ; if the intent was `p-4` |
| 218 | +as the override, the argument order is wrong). |
| 219 | + |
| 220 | +**Severity** : Info (requires intent inference ; flag for human |
| 221 | +review only) |
| 222 | + |
| 223 | +**Sibling skill** : `tailwind-impl-tailwind-merge` |
| 224 | + |
| 225 | +**Fix** : reverse the argument order so the override is last. |
| 226 | + |
| 227 | +### Rule R-10 : Plugin Missing Default Theme Values |
| 228 | + |
| 229 | +**Scans** : `*.js`, `*.ts` files exporting a Tailwind plugin |
| 230 | + |
| 231 | +**Detect** : the plugin body calls `theme('someNamespace')` AND the |
| 232 | +plugin's second-arg config does NOT define |
| 233 | +`theme.extend.someNamespace`. |
| 234 | + |
| 235 | +**Severity** : Warning (plugin works only when consumer happens to |
| 236 | +configure the namespace, fails silently otherwise) |
| 237 | + |
| 238 | +**Sibling skill** : `tailwind-impl-plugins-custom` |
| 239 | + |
| 240 | +**Fix** : ship defaults via the `plugin(fn, { theme: { ... } })` |
| 241 | +second argument. |
| 242 | + |
| 243 | +## Report Format |
| 244 | + |
| 245 | +``` |
| 246 | +Tailwind Validator Report |
| 247 | +========================= |
| 248 | +Project state : v4 detected (tailwindcss@^4.0.5) |
| 249 | +Scan scope : src/ tests/ tailwind.config.js postcss.config.mjs |
| 250 | +Files scanned : 412 |
| 251 | +Issues : 7 (1 error, 3 warnings, 3 info) |
| 252 | +
|
| 253 | +--- |
| 254 | +
|
| 255 | +[ERROR] R-01 : dynamic class string |
| 256 | + File : src/components/Tag.tsx:14 |
| 257 | + Code : `className={`bg-${color}-100 text-${color}-800`}` |
| 258 | + Cite : tailwind-errors-dynamic-classes (Fix 1, static map) |
| 259 | + Suggested : replace with lookup object indexed by `color`. |
| 260 | +
|
| 261 | +[ERROR] R-02 : scoped @apply without @reference |
| 262 | + File : src/components/Hero.vue:42 |
| 263 | + Block : <style scoped> @apply text-2xl font-bold </style> |
| 264 | + Cite : tailwind-impl-apply-directive |
| 265 | + Suggested : prepend `@reference "../app.css";` inside <style>. |
| 266 | +
|
| 267 | +[WARNING] R-03 : forbidden v3 key in v4 config |
| 268 | + File : tailwind.config.js:8 |
| 269 | + Key : safelist |
| 270 | + Cite : tailwind-impl-migration-v3-v4 (Method 7) |
| 271 | + Suggested : move to `@source inline(...)` in src/app.css and |
| 272 | + remove from JS config. |
| 273 | +
|
| 274 | +... etc |
| 275 | +``` |
| 276 | + |
| 277 | +Per-issue fields : |
| 278 | + |
| 279 | +- Severity tag in brackets |
| 280 | +- Rule code |
| 281 | +- File and line number |
| 282 | +- The offending snippet |
| 283 | +- The sibling skill name (NOT a URL ; the in-package skill reference) |
| 284 | +- A one-line suggested fix |
| 285 | + |
| 286 | +## Cross-Reference Index |
| 287 | + |
| 288 | +| Rule | Authoritative Skill | |
| 289 | +|------|---------------------| |
| 290 | +| R-01 dynamic classes | `tailwind-errors-dynamic-classes` | |
| 291 | +| R-02 scoped @apply | `tailwind-impl-apply-directive` | |
| 292 | +| R-03 v3 keys in v4 | `tailwind-impl-migration-v3-v4` | |
| 293 | +| R-04 undefined custom class | `tailwind-impl-config-v3`, `tailwind-impl-config-v4` | |
| 294 | +| R-05 variant stack order | `tailwind-syntax-variants` | |
| 295 | +| R-06 bare border/ring | `tailwind-impl-migration-v3-v4` | |
| 296 | +| R-07 em-dash | workspace CLAUDE.md | |
| 297 | +| R-08 prop-to-class shape | `tailwind-core-design-system` | |
| 298 | +| R-09 twMerge ordering | `tailwind-impl-tailwind-merge` | |
| 299 | +| R-10 plugin theme defaults | `tailwind-impl-plugins-custom` | |
| 300 | + |
| 301 | +## CI Integration |
| 302 | + |
| 303 | +The validator is ADVISORY by default. Exit code is 0 unless errors |
| 304 | +exceed the configured threshold. Sample GitHub Actions step : |
| 305 | + |
| 306 | +```yaml |
| 307 | +- name: Tailwind audit |
| 308 | + run: | |
| 309 | + claude --skill tailwind-agents-validator . > tailwind-report.txt |
| 310 | + cat tailwind-report.txt |
| 311 | + # fail only if errors > 0 |
| 312 | + grep -q '^\[ERROR\]' tailwind-report.txt && exit 1 || exit 0 |
| 313 | +``` |
| 314 | +
|
| 315 | +NEVER block PRs on info or warning levels. Errors (R-01, R-02) |
| 316 | +indicate real production bugs and SHOULD block. |
| 317 | +
|
| 318 | +## Sequencing With Sibling Skills |
| 319 | +
|
| 320 | +The validator is NOT a substitute for the per-area skills. When a |
| 321 | +finding cites `tailwind-errors-dynamic-classes`, the developer (or |
| 322 | +the follow-up agent) opens that skill for the full fix catalogue. |
| 323 | + |
| 324 | +Recommended workflow : |
| 325 | + |
| 326 | +1. Run `tailwind-agents-validator`. |
| 327 | +2. For each error : open the cited skill, apply the documented fix. |
| 328 | +3. Re-run the validator. Iterate until errors = 0. |
| 329 | +4. Optional : address warnings and info as time allows. |
| 330 | + |
| 331 | +## References |
| 332 | + |
| 333 | +- `references/methods.md` : per-rule detection commands (grep, AST, |
| 334 | + PostCSS) and the v3-or-v4 heuristic in detail |
| 335 | +- `references/examples.md` : sample input codebase plus the validator |
| 336 | + report it should produce |
| 337 | +- `references/anti-patterns.md` : ten high-impact violations the |
| 338 | + validator catches, with before/after |
| 339 | + |
| 340 | +## Sources |
| 341 | + |
| 342 | +- All sibling skills in this package |
| 343 | +- Project CLAUDE.md (workspace conventions) |
0 commit comments