stylelint-plugin-rhythmguard 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,201 +4,36 @@
4
4
 
5
5
  # stylelint-plugin-rhythmguard
6
6
 
7
- Token governance for CSS and Tailwind. Enforce spacing scales, require design tokens, and catch arbitrary values before they ship.
7
+ Spacing scale and design-token governance for CSS and Tailwind. `padding: 13px` and `p-[13px]` get reported with the nearest on-scale values, and fixed to them when you ask.
8
8
 
9
9
  [![CI](https://img.shields.io/github/actions/workflow/status/petrilahdelma/stylelint-plugin-rhythmguard/ci.yml?branch=main&label=ci)](https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/actions/workflows/ci.yml)
10
10
  [![npm version](https://img.shields.io/npm/v/stylelint-plugin-rhythmguard.svg)](https://www.npmjs.com/package/stylelint-plugin-rhythmguard)
11
11
  [![npm downloads](https://img.shields.io/npm/dm/stylelint-plugin-rhythmguard.svg)](https://www.npmjs.com/package/stylelint-plugin-rhythmguard)
12
12
  [![License: MIT](https://img.shields.io/badge/license-MIT-white.svg)](./LICENSE)
13
- [![Node](https://img.shields.io/badge/node-%3E%3D18.18-black.svg)](https://nodejs.org/)
14
13
 
15
- Rhythmguard enforces scale and token discipline across spacing, radius, typography, size, and motion offsets in CSS declarations and Tailwind class strings.
14
+ Rhythmguard is scale-aware rather than a blanket ban: values on your scale pass, values off it are reported with the two nearest steps, and tokens are only ever suggested from a map you control. It works on CSS declarations through Stylelint and on Tailwind class strings through an ESLint companion, and it ships an audit CLI so you can measure drift and ratchet it down before enforcing anything.
16
15
 
17
- Built for teams that want:
16
+ What it is not: it does not check colors or hex values, the Stylelint rules do not see Tailwind class strings (that is the separate ESLint companion below), and it does not parse SCSS in the audit. Pair it with a color linter if you need one; do not expect one tool to do both.
18
17
 
19
- - zero random spacing values in production
20
- - token-first workflows with autofix migration
21
- - Tailwind arbitrary value governance (`p-[13px]` → `p-[12px]`)
22
- - consistent layout rhythm across components and pages
23
-
24
- ## Quick Start: Next.js + Tailwind
18
+ ## Start here
25
19
 
26
20
  ```bash
27
- npm install --save-dev stylelint stylelint-plugin-rhythmguard
28
- ```
29
-
30
- **.stylelintrc.json:**
31
-
32
- ```json
33
- {
34
- "extends": ["stylelint-plugin-rhythmguard/configs/tailwind"]
35
- }
36
- ```
37
-
38
- **eslint.config.js** (for Tailwind class-string governance):
39
-
40
- ```js
41
- import rhythmguard from 'stylelint-plugin-rhythmguard/eslint';
42
-
43
- export default [
44
- {
45
- plugins: { 'rhythmguard-tailwind': rhythmguard },
46
- rules: {
47
- 'rhythmguard-tailwind/tailwind-class-use-scale': [
48
- 'error',
49
- { scale: [0, 4, 8, 12, 16, 24, 32] }
50
- ],
51
- },
52
- },
53
- ];
21
+ npx rhythmguard
54
22
  ```
55
23
 
56
- This gives you spacing governance in both CSS files and JSX/TSX templates.
57
-
58
- ## Rule Matrix
59
-
60
- <p align="center">
61
- <img src="https://raw.githubusercontent.com/petrilahdelma/stylelint-plugin-rhythmguard/main/assets/rhythmguard-rules.svg" width="100%" alt="Rhythmguard rule matrix visual" />
62
- </p>
63
-
64
- | Rule | What it does | Autofix |
65
- | --- | --- | --- |
66
- | `rhythmguard/use-scale` | Enforces spacing values must be on your configured scale | Yes, nearest safe value |
67
- | `rhythmguard/prefer-token` | Enforces token usage over raw spacing literals | Yes, with `tokenMap` |
68
- | `rhythmguard/no-offscale-transform` | Enforces scale-aligned `translate*` motion offsets | Yes, nearest safe value |
69
- | `rhythmguard/use-motion-scale` | Enforces opt-in duration/delay rhythm and flags raw easing curves | Yes, for duration/delay values |
70
-
71
- ## Demo
72
-
73
- <p align="center">
74
- <a href="https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/blob/main/assets/rhythmguard-campaign-60s.webm">
75
- <img src="https://raw.githubusercontent.com/petrilahdelma/stylelint-plugin-rhythmguard/main/assets/rhythmguard-campaign-60s.gif" width="100%" alt="Rhythmguard 60-second demo" />
76
- </a>
77
- </p>
78
-
79
- I built Rhythmguard after 20 years of watching teams ignore spacing scales and ship arbitrary pixel values everywhere.
80
-
81
- ## Try It in Your Browser
82
-
83
- **[petrilahdelma.github.io/stylelint-plugin-rhythmguard](https://petrilahdelma.github.io/stylelint-plugin-rhythmguard/)** — paste CSS, see violations and token opportunities live. No install, no config.
84
-
85
- ## Audit Before You Enforce
86
-
87
- Use the audit CLI to create a design-system drift report before turning rules into hard CI gates:
88
-
89
- ```bash
90
- npx rhythmguard audit ./src
91
- npx rhythmguard audit ./src --format markdown
92
- npx rhythmguard audit ./src --json
93
- npx rhythmguard audit . --ignore "apps/legacy/**" --ignore "vendor/**"
94
- npx rhythmguard audit ./src --write-baseline
95
- npx rhythmguard audit ./src --since-baseline --fail-on-new-drift
96
- npx rhythmguard audit ./src --staged --max-findings 0
97
- npx rhythmguard audit ./src --token-source ./tokens.json
98
- npx rhythmguard audit ./src --token-source ./theme.css --token-source-format css
99
- npx rhythmguard audit ./src --include-motion
100
- npx rhythmguard audit ./src --format html --output rhythmguard-report.html
101
- npx rhythmguard audit --schema
102
- ```
103
-
104
- The report covers authored CSS declarations, Tailwind arbitrary spacing values in common template/source files, and token-contract drift such as missing spacing tokens, unused spacing tokens, repeated raw values that deserve token review, raw values that match known tokens, conflicting token values, and opt-in motion rhythm drift. Scan paths are scoped to the directory argument. Use `--ignore`, `.rhythmguardignore`, or `--ignore-path` for generated or legacy subtrees, then add baselines and CI thresholds when you are ready to gate new drift. Markdown output is PR-ready for UX developers, UX designers, and design-system owners:
105
-
106
- ```md
107
- # Rhythmguard Design-System Audit
108
-
109
- | Metric | Value |
110
- | --- | ---: |
111
- | CSS files scanned | 47 |
112
- | Template files scanned | 83 |
113
- | Files with issues | 12 |
114
- | Total findings | 52 |
115
- | Scale cleanliness | 91% |
116
- | New findings | 3 |
117
- ```
118
-
119
- ### Audit config and external token sources
120
-
121
- For large codebases, put shared audit settings in `.rhythmguardrc.json`:
122
-
123
- ```json
124
- {
125
- "audit": {
126
- "ignore": ["legacy/**", "generated/**"],
127
- "tokenSources": [
128
- "./tokens.json",
129
- { "path": "./src/theme.css", "format": "css" }
130
- ],
131
- "tokenKind": "spacing",
132
- "includeMotion": false,
133
- "tokenCandidateMinCount": 2,
134
- "minCleanliness": 90
135
- }
136
- }
137
- ```
138
-
139
- `rhythmguard audit` loads `.rhythmguardrc.json` automatically when present. Use `--config <file>` for another config, `--no-config` to skip config discovery, and `--token-source <file>` for extra canonical token files. Token source paths in config files resolve from the config file directory; CLI token source paths resolve from the current working directory. Supported source formats are CSS custom properties and Tailwind v4 `@theme`, flat JSON maps, Style Dictionary JSON, and DTCG JSON.
140
-
141
- ### Audit JSON 2.0 and API
142
-
143
- In Rhythmguard 2.0, `--format json` emits the stable audit contract:
144
-
145
- ```json
146
- {
147
- "schemaVersion": "2.0",
148
- "command": { "directory": "./src", "scanScope": "full" },
149
- "summary": { "totalFindings": 12, "scaleCleanliness": 94 },
150
- "scanned": { "cssFiles": 10, "templateFiles": 20 },
151
- "contracts": {
152
- "scale": {},
153
- "tokens": {},
154
- "motion": {}
155
- },
156
- "findings": {
157
- "css": [],
158
- "tailwind": [],
159
- "motion": []
160
- },
161
- "baseline": null
162
- }
163
- ```
164
-
165
- Use `--format json-v1` for the pre-2.0 JSON shape during migration.
166
-
167
- Programmatic usage:
168
-
169
- ```js
170
- const {
171
- createAuditReport,
172
- toAuditContractReport,
173
- } = require('stylelint-plugin-rhythmguard/audit');
174
-
175
- const report = await createAuditReport({ dir: './src', noConfig: true });
176
- const contract = toAuditContractReport(report);
177
- ```
178
-
179
- ## Installation
24
+ No install, no config. It detects your stack and token files, infers your spacing scale from your own tokens, audits the current directory, and prints the exact `.stylelintrc.json` (and ESLint snippet for Tailwind) to paste. Then:
180
25
 
181
26
  ```bash
182
27
  npm install --save-dev stylelint stylelint-plugin-rhythmguard
183
28
  ```
184
29
 
185
- ## Drop-In for Existing Projects (Recommended)
186
-
187
- If your project already uses Stylelint, you only need one command and one config block:
188
-
189
- ```bash
190
- npm install --save-dev stylelint-plugin-rhythmguard
191
- ```
192
-
193
30
  ```json
194
31
  {
195
32
  "extends": ["stylelint-plugin-rhythmguard/configs/recommended"]
196
33
  }
197
34
  ```
198
35
 
199
- ## Quick Start
200
-
201
- ### Tailwind config
36
+ That enables `rhythmguard/use-scale` on spacing properties with the default 4px scale. Tailwind projects use the `tailwind` config instead, which also extracts spacing tokens from `@theme`:
202
37
 
203
38
  ```json
204
39
  {
@@ -206,628 +41,70 @@ npm install --save-dev stylelint-plugin-rhythmguard
206
41
  }
207
42
  ```
208
43
 
209
- ### Recommended config
210
-
211
- ```json
212
- {
213
- "extends": ["stylelint-plugin-rhythmguard/configs/recommended"]
214
- }
215
- ```
216
-
217
- ### Strict config
218
-
219
- ```json
220
- {
221
- "extends": ["stylelint-plugin-rhythmguard/configs/strict"]
222
- }
223
- ```
224
-
225
- `strict` intentionally delegates transform translation enforcement to `rhythmguard/no-offscale-transform` to reduce overlapping warnings from `use-scale`.
226
-
227
- ### Expanded config
228
-
229
- ```json
230
- {
231
- "extends": ["stylelint-plugin-rhythmguard/configs/expanded"]
232
- }
233
- ```
234
-
235
- `expanded` enables scale enforcement for spacing + radius + typography + size property groups.
236
-
237
- ### Logical config
238
-
239
- ```json
240
- {
241
- "extends": ["stylelint-plugin-rhythmguard/configs/logical"]
242
- }
243
- ```
244
-
245
- `logical` composes Rhythmguard strict mode with `stylelint-plugin-logical-css` recommended rules.
246
-
247
- ### Migration config
248
-
249
- ```json
250
- {
251
- "extends": ["stylelint-plugin-rhythmguard/configs/migration"]
252
- }
253
- ```
254
-
255
- `migration` keeps on-scale numeric values temporarily while auto-building token mappings from CSS custom properties and optional Tailwind spacing config.
256
-
257
- ### React / Next.js + Tailwind config
258
-
259
- ```json
260
- {
261
- "extends": ["stylelint-plugin-rhythmguard/configs/react-tailwind"]
262
- }
263
- ```
264
-
265
- `react-tailwind` extends the tailwind config with CSS Modules overrides (spacing + radius enforcement) and ignores Next.js build directories.
266
-
267
- ### Motion config
268
-
269
- ```json
270
- {
271
- "extends": ["stylelint-plugin-rhythmguard/configs/motion"]
272
- }
273
- ```
274
-
275
- `motion` enables opt-in duration/delay rhythm checks with `rhythmguard/use-motion-scale`.
276
-
277
- Stable shared config entry points:
278
-
279
- - `stylelint-plugin-rhythmguard/configs/recommended`
280
- - `stylelint-plugin-rhythmguard/configs/strict`
281
- - `stylelint-plugin-rhythmguard/configs/tailwind`
282
- - `stylelint-plugin-rhythmguard/configs/react-tailwind`
283
- - `stylelint-plugin-rhythmguard/configs/expanded`
284
- - `stylelint-plugin-rhythmguard/configs/logical`
285
- - `stylelint-plugin-rhythmguard/configs/migration`
286
- - `stylelint-plugin-rhythmguard/configs/motion`
287
-
288
- Framework-specific setup for Vue, Lit, Astro, and SvelteKit: [`docs/FRAMEWORKS.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/FRAMEWORKS.md)
289
-
290
- ## Comparison and Migration Recipes
291
-
292
- - Side-by-side tool fit guide with migration snippets: [`docs/COMPARISON.md`](https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/blob/main/docs/COMPARISON.md)
293
- - Audit 2.0 validation and roadmap: [`docs/AUDIT_2_VALIDATION.md`](https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/blob/main/docs/AUDIT_2_VALIDATION.md)
294
- - CI baseline rollout recipe: [`docs/CI_ADOPTION.md`](https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/blob/main/docs/CI_ADOPTION.md)
295
- - Programmatic dashboard and Figma-friendly export examples: [`docs/AUDIT_API_EXAMPLES.md`](https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/blob/main/docs/AUDIT_API_EXAMPLES.md)
296
- - Real-world before/after excerpts from public repos: [`docs/ADOPTION_DIFFS.md`](https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/blob/main/docs/ADOPTION_DIFFS.md)
297
- - Distribution submissions to Stylelint discovery surfaces: [`docs/DISTRIBUTION.md`](https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/blob/main/docs/DISTRIBUTION.md)
298
-
299
- ### Full custom setup
300
-
301
- ```json
302
- {
303
- "plugins": ["stylelint-plugin-rhythmguard"],
304
- "rules": {
305
- "rhythmguard/use-scale": [
306
- true,
307
- {
308
- "preset": "rhythmic-4",
309
- "propertyGroups": ["spacing", "radius"],
310
- "propertyScales": {
311
- "font-size": [12, 14, 16, 20, 24]
312
- },
313
- "units": ["px", "rem", "em"],
314
- "unitStrategy": "convert",
315
- "baseFontSize": 16,
316
- "tokenPattern": "^--space-",
317
- "tokenFunctions": ["var", "theme", "token"],
318
- "allowNegative": true,
319
- "allowPercentages": true,
320
- "fixToScale": true,
321
- "enforceInsideMathFunctions": true,
322
- "mathFunctionArguments": {
323
- "clamp": [1, 3]
324
- }
325
- }
326
- ],
327
- "rhythmguard/prefer-token": [
328
- true,
329
- {
330
- "tokenPattern": "^--space-",
331
- "allowNumericScale": false,
332
- "tokenMapFromCssCustomProperties": true,
333
- "tokenMapFromTailwindSpacing": true,
334
- "tailwindConfigPath": "./tailwind.config.mjs",
335
- "tokenMap": {
336
- "4px": "var(--space-1)",
337
- "8px": "var(--space-2)",
338
- "12px": "var(--space-3)",
339
- "16px": "var(--space-4)"
340
- }
341
- }
342
- ],
343
- "rhythmguard/no-offscale-transform": [
344
- true,
345
- {
346
- "scale": [0, 4, 8, 12, 16, 24, 32]
347
- }
348
- ]
349
- }
350
- }
351
- ```
352
-
353
- ### Presets and custom scales
354
-
355
- Preset-based setup:
356
-
357
- ```json
358
- {
359
- "rules": {
360
- "rhythmguard/use-scale": [true, { "preset": "fibonacci" }]
361
- }
362
- }
363
- ```
364
-
365
- Custom scale setup:
366
-
367
- ```json
368
- {
369
- "rules": {
370
- "rhythmguard/use-scale": [true, { "customScale": [0, 6, 12, 18, 24, 36, 48] }]
371
- }
372
- }
373
- ```
374
-
375
- Scale resolution precedence:
376
-
377
- 1. `customScale` (highest priority)
378
- 2. `scale`
379
- 3. `preset`
380
- 4. default `rhythmic-4` scale
381
-
382
- ## Option Validation
383
-
384
- Rhythmguard validates `secondaryOptions` for each rule before linting declarations.
385
-
386
- - Unknown option names fail fast with Stylelint invalid option warnings.
387
- - Invalid option shapes fail fast (for example string vs array mismatches).
388
- - `properties` string entries are validated against supported scale-targetable CSS property names.
389
- - `propertyGroups` values are validated against built-in groups: `spacing`, `radius`, `typography`, and `size`.
390
- - Math function argument maps are validated per function (`calc`, `clamp`, `min`, `max`) and positive 1-based argument indexes.
391
-
392
- Example typo that now fails immediately:
393
-
394
- ```json
395
- {
396
- "rules": {
397
- "rhythmguard/use-scale": [true, { "sevverity": "warning" }]
398
- }
399
- }
400
- ```
401
-
402
- ## Built-in Scale Presets
403
-
404
- | Preset | Pattern | Scale |
405
- | --- | --- | --- |
406
- | `rhythmic-4` | 4pt rhythm | `[0,4,8,12,16,24,32,40,48,64]` |
407
- | `rhythmic-8` | 8pt rhythm | `[0,8,16,24,32,40,48,64,80,96]` |
408
- | `product-material-8dp` | Material 8dp baseline + 4dp increments | `[0,4,8,12,16,24,32,40,48,56,64,72,80]` |
409
- | `product-atlassian-8px` | Atlassian-like product spacing progression | `[0,2,4,6,8,12,16,20,24,32,40,48,64,80]` |
410
- | `product-carbon-2x` | Carbon 2x spacing progression | `[0,2,4,8,12,16,24,32,40,48,64,80]` |
411
- | `editorial-baseline-4` | editorial baseline rhythm at 4-unit cadence | `[0,4,8,12,16,20,24,28,32,40,48,56,64]` |
412
- | `editorial-baseline-6` | editorial baseline rhythm at 6-unit cadence | `[0,6,12,18,24,30,36,48,60,72]` |
413
- | `compact` | dense UI spacing | `[0,2,4,6,8,12,16,20,24,32]` |
414
- | `fibonacci` | Fibonacci progression | `[0,2,3,5,8,13,21,34,55,89]` |
415
- | `powers-of-two` | geometric doubling | `[0,2,4,8,16,32,64,128]` |
416
- | `golden-ratio` | ratio 1.618 | generated modular sequence |
417
- | `modular-major-second` | ratio 1.125 | generated modular sequence |
418
- | `modular-minor-third` | ratio 1.2 | generated modular sequence |
419
- | `modular-major-third` | ratio 1.25 | generated modular sequence |
420
- | `modular-augmented-fourth` | ratio 1.414 | generated modular sequence |
421
- | `modular-perfect-fourth` | ratio 1.333 | generated modular sequence |
422
- | `modular-perfect-fifth` | ratio 1.5 | generated modular sequence |
423
-
424
- Aliases:
425
-
426
- - `4pt` → `rhythmic-4`
427
- - `8pt` → `rhythmic-8`
428
- - `material` → `product-material-8dp`
429
- - `atlassian-8` → `product-atlassian-8px`
430
- - `carbon` → `product-carbon-2x`
431
- - `baseline-4` → `editorial-baseline-4`
432
- - `baseline-6` → `editorial-baseline-6`
433
- - `golden` → `golden-ratio`
434
- - `major-second` → `modular-major-second`
435
- - `minor-third` → `modular-minor-third`
436
- - `major-third` → `modular-major-third`
437
- - `augmented-fourth` → `modular-augmented-fourth`
438
- - `perfect-fourth` → `modular-perfect-fourth`
439
- - `perfect-fifth` → `modular-perfect-fifth`
440
-
441
- ### Preset Rationale
442
-
443
- - Product presets are based on widely-used design-system spacing frameworks.
444
- - Editorial presets model baseline-grid cadence used in long-form typography and column layouts.
445
- - Theory presets expose mathematically-derived modular scales from design theory and typographic proportion systems.
446
- - Full research notes and sources are documented in [`docs/SCALE_RESEARCH.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/SCALE_RESEARCH.md).
447
-
448
- ## Community Scale Registry
449
-
450
- Rhythmguard supports community-contributed scale presets from `scales/community/*.json`.
451
-
452
- ### Current community scales
453
-
454
- | Preset | Base | Pattern | Contributor |
455
- | --- | --- | --- | --- |
456
- | `product-decimal-10` | `10` | Decimal-friendly dashboard/product cadence | [Petri Lahdelma](https://github.com/PetriLahdelma) |
457
-
458
- ### Contribute a scale
459
-
460
- 1. Scaffold a new scale file:
461
-
462
- ```bash
463
- npm run scales:add -- --name my-team-scale --base 8 --steps 0,4,8,12,16,24,32
464
- ```
465
-
466
- 2. Validate:
467
-
468
- ```bash
469
- npm run scales:validate
470
- ```
471
-
472
- 3. Open a PR with your scale JSON.
473
-
474
- Full specification and policy: [`docs/COMMUNITY_SCALES.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/COMMUNITY_SCALES.md).
475
-
476
- If your scale is private or very niche, keep it in your project config with `customScale` instead of contributing it to the shared registry.
477
-
478
- ## Rule Details
479
-
480
- ### `rhythmguard/use-scale`
481
-
482
- Enforces spacing literals to stay on a configured numeric scale.
483
-
484
- Checks:
485
-
486
- - `margin*`, `padding*`
487
- - `gap`, `row-gap`, `column-gap`
488
- - `inset*`, `scroll-margin*`, `scroll-padding*`
489
- - `translate`, `translate-x`, `translate-y`, `translate-z`
490
- - `transform` translation functions (`translate`, `translateX`, `translateY`, `translateZ`, `translate3d`)
491
- - optional property groups:
492
- - `radius` (`border-radius*`, corner radii, `outline-offset`)
493
- - `typography` (`font-size`, `line-height`, `letter-spacing`, `word-spacing`)
494
- - `size` (`width`, `height`, min/max size, logical `inline-size`/`block-size`)
495
-
496
- Example:
497
-
498
- ```css
499
- /* ❌ Off-scale */
500
- .card {
501
- margin: 13px;
502
- transform: translateY(18px);
503
- }
504
-
505
- /* ✅ On-scale */
506
- .card {
507
- margin: 12px;
508
- transform: translateY(16px);
509
- }
510
- ```
511
-
512
- Options:
513
-
514
- | Option | Type | Default | Description |
515
- | --- | --- | --- | --- |
516
- | `preset` | `string` | `rhythmic-4` | Selects a built-in spacing scale |
517
- | `customScale` | `Array<number|string>` | `undefined` | Highest-priority custom scale override |
518
- | `scale` | `Array<number|string>` | `[0,4,8,12,16,24,32,40,48,64]` | Allowed spacing values |
519
- | `units` | `string[]` | `['px','rem','em']` | Units considered for scale enforcement |
520
- | `unitStrategy` | `'convert' \| 'exact'` | `'convert'` | `convert`: compare via px conversion (`px/rem/em`). `exact`: compare against same-unit scale values (for example `vw`, `cqi`) |
521
- | `baseFontSize` | `number` | `16` | Used for `rem`/`em` conversion |
522
- | `tokenPattern` | `string` | `^--space-` | Regex for accepted token variable names |
523
- | `tokenFunctions` | `string[]` | `['var','theme','token']` | Functions treated as tokenized values |
524
- | `allowNegative` | `boolean` | `true` | Allows negative scale values |
525
- | `allowPercentages` | `boolean` | `true` | Allows `%` values without scale checks |
526
- | `fixToScale` | `boolean` | `true` | Enables nearest-value autofix |
527
- | `enforceInsideMathFunctions` | `boolean` | `false` | Lints `calc()/clamp()/min()/max()` internals |
528
- | `mathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Restricts linting to specific 1-based argument indexes per math function |
529
- | `ignoreMathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Excludes specific 1-based argument indexes per math function |
530
- | `propertyGroups` | `Array<'spacing' \| 'radius' \| 'typography' \| 'size' \| 'motion'>` | `['spacing']` | Selects built-in property groups when `properties` is not provided |
531
- | `properties` | `Array<string|RegExp>` | built-in spacing patterns | Override targeted property set; string values may be supported property names or regex-like strings (`/pattern/flags`) |
532
- | `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides (supports exact names or `/regex/flags` keys; stateful `g`/`y` flags are normalized for deterministic matching) |
533
-
534
- ### `rhythmguard/prefer-token`
535
-
536
- Enforces token usage for spacing declarations. This is ideal once your token system is stable.
537
-
538
- Example:
539
-
540
- ```css
541
- /* ❌ Raw literals */
542
- .stack {
543
- gap: 12px;
544
- padding: 16px;
545
- }
546
-
547
- /* ✅ Tokenized */
548
- .stack {
549
- gap: var(--space-3);
550
- padding: var(--space-4);
551
- }
552
- ```
553
-
554
- Options:
555
-
556
- | Option | Type | Default | Description |
557
- | --- | --- | --- | --- |
558
- | `tokenPattern` | `string` | `^--space-` | Regex for accepted token variable names |
559
- | `tokenFunctions` | `string[]` | `['var','theme','token']` | Functions treated as tokenized values |
560
- | `allowNumericScale` | `boolean` | `false` | Temporary migration mode to permit on-scale literals |
561
- | `preset` | `string` | `rhythmic-4` | Selects a built-in scale used in migration mode |
562
- | `customScale` | `Array<number|string>` | `undefined` | Highest-priority custom scale override |
563
- | `scale` | `Array<number|string>` | `[0,4,8,12,16,24,32,40,48,64]` | Used when `allowNumericScale` is enabled |
564
- | `baseFontSize` | `number` | `16` | Used for scale checks with `rem`/`em` |
565
- | `unitStrategy` | `'convert' \| 'exact'` | `'convert'` | Matching strategy when `allowNumericScale` is enabled |
566
- | `units` | `string[]` | `['px','rem','em']` | Units considered for numeric scale checks |
567
- | `enforceInsideMathFunctions` | `boolean` | `false` | Lints `calc()/clamp()/min()/max()` internals |
568
- | `mathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Restricts linting to specific 1-based argument indexes per math function |
569
- | `ignoreMathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Excludes specific 1-based argument indexes per math function |
570
- | `tokenMap` | `Record<string,string>` | `{}` | Enables autofix from raw value to token |
571
- | `tokenMapFile` | `string` | `null` | JSON file path to merge additional token mappings (supports flat, Style Dictionary, and W3C DTCG formats) |
572
- | `tokenMapFromCssCustomProperties` | `boolean` | `false` | Auto-builds mappings from matching custom property declarations in the same stylesheet |
573
- | `tokenMapFromTailwindSpacing` | `boolean` | `false` | Auto-builds mappings from `theme.spacing` and `theme.extend.spacing` in Tailwind config |
574
- | `tailwindConfigPath` | `string` | `null` | Path to Tailwind config used by `tokenMapFromTailwindSpacing` (`.js`, `.cjs`, `.mjs`) |
575
- | `ignoreValues` | `string[]` | CSS global keywords + `auto` | Skips keyword literals |
576
- | `propertyGroups` | `Array<'spacing' \| 'radius' \| 'typography' \| 'size'>` | `['spacing']` | Selects built-in property groups when `properties` is not provided |
577
- | `properties` | `Array<string|RegExp>` | built-in spacing patterns | Override targeted property set; string values may be supported property names or regex-like strings (`/pattern/flags`) |
578
- | `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides for numeric migration mode (stateful `g`/`y` flags are normalized for deterministic matching) |
579
-
580
- ### `rhythmguard/no-offscale-transform`
581
-
582
- Specialized guardrail for motion spacing consistency in translation transforms.
583
-
584
- Example:
585
-
586
- ```css
587
- /* ❌ Off-scale motion */
588
- .toast {
589
- transform: translateY(18px) scale(1);
590
- }
591
-
592
- /* ✅ Motion on spacing scale */
593
- .toast {
594
- transform: translateY(16px) scale(1);
595
- }
596
- ```
597
-
598
- Options:
599
-
600
- `rhythmguard/no-offscale-transform` accepts the same scale options as `rhythmguard/use-scale` (including `unitStrategy`, math argument targeting, and deterministic autofix), but only for transform translation properties. Its secondary options are also validated for unknown keys and invalid value shapes.
601
-
602
- ### `rhythmguard/use-motion-scale`
603
-
604
- Opt-in guardrail for duration, delay, and easing rhythm.
605
-
606
- Example:
607
-
608
- ```css
609
- /* ❌ Off-scale timing + raw easing */
610
- .button {
611
- transition: opacity 175ms cubic-bezier(.2, 0, 0, 1);
612
- }
613
-
614
- /* ✅ Timing on motion scale */
615
- .button {
616
- transition: opacity 150ms var(--ease-snappy);
617
- }
618
- ```
619
-
620
- Options:
621
-
622
- | Option | Type | Default | Description |
623
- | --- | --- | --- | --- |
624
- | `durationScale` | `number[]` | `[0,75,100,150,200,300,500,700,1000]` | Allowed duration and delay values in milliseconds |
625
- | `durationUnits` | `Array<'ms' \| 's'>` | `['ms','s']` | Time units considered by the rule |
626
- | `fixToScale` | `boolean` | `true` | Autofixes simple duration/delay values to the nearest scale value |
627
- | `easingTokenMap` | `Record<string,string>` | `{}` | Optional exact replacements for raw easing functions |
628
-
629
- Tailwind class strings can use the ESLint companion rule `rhythmguard-tailwind/tailwind-class-use-motion-scale` for `duration-[...]`, `delay-[...]`, and `ease-[...]` arbitrary values.
630
-
631
- ## Tailwind CSS Integration
632
-
633
- Rhythmguard works well in Tailwind projects, but it enforces what Stylelint can parse: CSS declarations.
634
-
635
- ### What Rhythmguard covers in Tailwind projects
636
-
637
- - custom CSS in `globals.css`, `components.css`, `utilities.css`
638
- - CSS Modules (for example `*.module.css`)
639
- - declarations inside `@layer` blocks
640
-
641
- ### Tailwind v4 @theme tokens
642
-
643
- The `tailwind` config preset automatically extracts spacing tokens from Tailwind v4 `@theme` blocks and uses them for `prefer-token` enforcement. Raw values like `padding: 16px` are autofixed to `padding: var(--spacing-4)`.
644
-
645
- See [`docs/TAILWIND.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/TAILWIND.md) for full setup.
646
-
647
- ### What the Stylelint layer does not cover
648
-
649
- - Tailwind class strings in templates/JSX/TSX, for example:
650
- - `class="p-4 gap-2"`
651
- - `class="p-[13px] translate-y-[18px]"`
652
-
653
- Those are not Stylelint declaration nodes, so they are outside Stylelint rule scope. Use the ESLint companion rule below for scale-aware class-string enforcement.
654
-
655
- ### Companion ESLint layer for class strings
656
-
657
- Rhythmguard now ships an ESLint companion export for class-string governance:
44
+ For class strings in JSX, TSX, Vue, Svelte or Astro, add the ESLint companion:
658
45
 
659
46
  ```js
660
- // eslint.config.js (flat config)
47
+ // eslint.config.js
661
48
  import rhythmguard from 'stylelint-plugin-rhythmguard/eslint';
662
49
 
663
50
  export default [
664
51
  {
665
- plugins: {
666
- 'rhythmguard-tailwind': rhythmguard,
667
- },
668
- rules: {
669
- 'rhythmguard-tailwind/tailwind-class-use-scale': ['error', { scale: [0, 4, 8, 12, 16, 24, 32] }],
670
- },
52
+ plugins: { 'rhythmguard-tailwind': rhythmguard },
53
+ rules: { 'rhythmguard-tailwind/tailwind-class-use-scale': ['error', { scale: [0, 4, 8, 12, 16, 24, 32] }] },
671
54
  },
672
55
  ];
673
56
  ```
674
57
 
675
- This rule targets arbitrary spacing utilities such as `p-[13px]`, `gap-[18px]`, `translate-x-[10px]`, and autofixes to the nearest configured scale value.
676
-
677
- #### Supported patterns
678
-
679
- The rule checks every string literal in your code, so it works automatically with common utility functions:
680
-
681
- - `cn("p-[13px]")` / `cn("p-[13px]", condition && "m-[7px]")`
682
- - `clsx("p-[13px]", "gap-[18px]")`
683
- - `twMerge("p-[13px]", otherClasses)`
684
- - `cva("base", { variants: { size: { sm: "p-[5px]" } } })`
685
- - `<div className={cn("p-[13px]")} />`
686
-
687
- No extra config needed — if the string contains an arbitrary spacing value, it gets caught and autofixed.
688
-
689
- ### Recommended stack for full Tailwind enforcement
690
-
691
- Use both layers:
692
-
693
- 1. Stylelint + Rhythmguard for CSS declaration governance.
694
- 2. Tailwind-aware class-string linting/formatting for template utility usage.
695
-
696
- Suggested setup:
697
-
698
- ```json
699
- {
700
- "extends": ["stylelint-plugin-rhythmguard/configs/tailwind"]
701
- }
702
- ```
703
-
704
- Then pair with:
705
-
706
- - `stylelint-plugin-rhythmguard/eslint` for arbitrary spacing class-string scale enforcement.
707
- - `eslint-plugin-tailwindcss` for broader class-string linting and conventions. If your policy is to ban every arbitrary value, enable its `tailwindcss/no-arbitrary-value` rule; use Rhythmguard when you want spacing-specific scale checks and nearest-value fixes.
708
- - `prettier-plugin-tailwindcss` for deterministic class ordering.
709
-
710
- Detailed setup reference: [`docs/TAILWIND.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/TAILWIND.md).
711
-
712
- ### Tailwind token function support
713
-
714
- By default, `tokenFunctions` includes `theme`, so values like `theme(spacing.4)` are treated as tokenized values.
715
-
716
- This keeps CSS declaration enforcement and template class-string enforcement separated but coordinated.
717
-
718
- ## Programmatic Presets
719
-
720
- ```js
721
- const rhythmguard = require('stylelint-plugin-rhythmguard');
722
-
723
- console.log(rhythmguard.presets.listScalePresetNames());
724
- console.log(rhythmguard.presets.listCommunityScalePresetNames());
725
- console.log(rhythmguard.presets.getCommunityScaleMetadata('product-decimal-10'));
726
- console.log(rhythmguard.presets.scales['rhythmic-4']);
727
- console.log(Object.keys(rhythmguard.eslint.rules));
728
- ```
729
-
730
- ## Token File Formats
731
-
732
- The `tokenMapFile` option supports multiple JSON formats:
733
-
734
- **Flat token-to-value:**
735
-
736
- ```json
737
- { "--spacing-4": "16px", "--spacing-3": "12px" }
738
- ```
739
-
740
- **Style Dictionary:**
741
-
742
- ```json
743
- { "--spacing-4": { "value": "16px" } }
744
- ```
745
-
746
- **W3C DTCG (Design Token Community Group):**
747
-
748
- ```json
749
- {
750
- "spacing": {
751
- "4": { "$value": "16px", "$type": "dimension" },
752
- "2": { "$value": "8px", "$type": "dimension" }
753
- }
754
- }
755
- ```
756
-
757
- Nested DTCG groups are walked recursively. The key path becomes the CSS variable name: `spacing.4` → `var(--spacing-4)`. Non-length values (colors, fonts) are ignored automatically.
758
-
759
- ## Autofix Philosophy
760
-
761
- Rhythmguard only applies deterministic fixes:
762
-
763
- - nearest scale value for numeric off-scale literals
764
- - explicit `tokenMap` replacements for token migration
765
-
766
- It will not guess token mappings without your map.
58
+ ## Rules
767
59
 
768
- ## Compatibility
769
-
770
- - Stylelint: `^16.0.0 || ^17.0.0`
771
- - Node.js: `>=18.18.0`
772
- - Module format: dual `require` + `import` entry points (CommonJS + ESM wrappers)
773
- - Note: Stylelint `16.0.0` has known autofix/API behavior differences; CI enforces floor compatibility and runs non-blocking full-suite observability on the floor version.
774
-
775
- ## Development
776
-
777
- ```bash
778
- npm install
779
- npm run lint
780
- npm test
781
- npm run test:coverage
782
- ```
60
+ | Rule | What it reports | Fix |
61
+ | --- | --- | --- |
62
+ | [`rhythmguard/use-scale`](docs/rules/use-scale.md) | Length values off the configured scale | Nearest scale value |
63
+ | [`rhythmguard/prefer-token`](docs/rules/prefer-token.md) | Raw literals where a design token exists | Token from your map |
64
+ | [`rhythmguard/no-offscale-transform`](docs/rules/no-offscale-transform.md) | Off-scale `translate*` offsets | Nearest scale value |
65
+ | [`rhythmguard/use-motion-scale`](docs/rules/use-motion-scale.md) | Off-scale durations and raw easing curves. Opt-in, experimental | Nearest duration |
66
+ | [`rhythmguard-tailwind/tailwind-class-use-scale`](docs/rules/tailwind-class-use-scale.md) | Off-scale Tailwind arbitrary spacing values (`p-[13px]`) in class strings | Nearest scale value |
67
+ | [`rhythmguard-tailwind/tailwind-class-use-motion-scale`](docs/rules/tailwind-class-use-motion-scale.md) | Off-scale `duration-[...]`, `delay-[...]`, raw `ease-[...]`. Opt-in | Nearest duration |
783
68
 
784
- ## Performance Benchmarking
69
+ Every rule validates its options up front. Unknown option names and wrong shapes are reported, never ignored.
785
70
 
786
- Compare runtime against `stylelint-scales` on a deterministic spacing corpus:
71
+ ## Configs
787
72
 
788
- ```bash
789
- npm run bench:perf
790
- ```
73
+ `recommended`, `strict`, `tailwind`, `react-tailwind`, `expanded`, `logical`, `migration`, `motion`, and `embed` for authors of shared configs (see [docs/FOR_CONFIG_AUTHORS.md](docs/FOR_CONFIG_AUTHORS.md)). All are `stylelint-plugin-rhythmguard/configs/<name>`. What each enables, the full custom setup, and the scale-selection precedence are in [docs/CONFIGS.md](docs/CONFIGS.md). Built-in and community scale presets are in [docs/SCALE_PRESETS.md](docs/SCALE_PRESETS.md).
791
74
 
792
- Benchmark with autofix enabled:
75
+ ## Audit before you enforce
793
76
 
794
77
  ```bash
795
- npm run bench:perf:fix
78
+ npx rhythmguard audit ./src --format markdown
79
+ npx rhythmguard audit ./src --write-baseline
80
+ npx rhythmguard audit ./src --since-baseline --fail-on-new-drift
81
+ npx rhythmguard audit ./src --format github
796
82
  ```
797
83
 
798
- Detailed methodology and custom args are documented in [`docs/BENCHMARKING.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/BENCHMARKING.md).
799
-
800
- ## Article
84
+ The audit scans CSS declarations, Tailwind class strings and your token contract, prints a scale-cleanliness score, and supports baselines so legacy codebases can gate only new drift. Formats: text, Markdown, JSON 2.0, HTML, and GitHub Actions annotations. Full reference in [docs/AUDIT.md](docs/AUDIT.md), rollout recipe in [docs/CI_ADOPTION.md](docs/CI_ADOPTION.md).
801
85
 
802
- - Dev.to: [Enforcing your spacing standards with Rhythmguard](https://dev.to/petrilahdelma/enforcing-your-spacing-standards-with-rhythmguard-a-custom-stylelint-plugin-1ojj)
86
+ `npx rhythmguard init` writes a starter config for your stack. `npx rhythmguard doctor` checks the setup.
803
87
 
804
- ## Used by and Community Examples
88
+ ## Guides
805
89
 
806
- Public codebases currently used for production migration examples:
90
+ - [Tailwind integration](docs/TAILWIND.md), including v4 `@theme` tokens and what each layer covers
91
+ - [Framework setup](docs/FRAMEWORKS.md) for Vue, Lit, Astro and SvelteKit
92
+ - [Comparison with adjacent plugins](docs/COMPARISON.md) and migration recipes
93
+ - [Real before/after excerpts](docs/ADOPTION_DIFFS.md) from public codebases
94
+ - [For shared-config authors](docs/FOR_CONFIG_AUTHORS.md): the `embed` entry point and how inference works per consumer
95
+ - [For coding agents](docs/FOR_AGENTS.md): a paste-ready `AGENTS.md` block and the commands to run
96
+ - [Quiet benchmark](docs/QUIET_BENCHMARK.md): findings on public design systems, checked on every change
97
+ - [Product direction](docs/STRATEGY_2026-09.md)
98
+ - Browser playground: [petrilahdelma.github.io/stylelint-plugin-rhythmguard](https://petrilahdelma.github.io/stylelint-plugin-rhythmguard/)
807
99
 
808
- - [PetriLahdelma/digitaltableteur-nextjs](https://github.com/PetriLahdelma/digitaltableteur-nextjs)
809
- - [PetriLahdelma/digitaltableteur](https://github.com/PetriLahdelma/digitaltableteur)
810
-
811
- Want your team listed here?
812
-
813
- 1. Open an issue with `used-by` in the title.
814
- 2. Include one before/after diff and your Rhythmguard config.
815
- 3. Add migration notes (false positives, rules enabled, rollout phase).
816
-
817
- ## Release Workflow
100
+ ## Compatibility
818
101
 
819
- 1. Create a GitHub release.
820
- 2. `release.yml` runs the Node/Stylelint matrix validation.
821
- 3. A tarball smoke test validates package exports and install behavior.
822
- 4. If `NPM_TOKEN` is configured in repository secrets, the package is published to npm with provenance (`npm publish --provenance`).
823
- 5. If `NPM_TOKEN` is not configured, publish is skipped with an explicit workflow notice.
824
- 6. `post-publish-smoke.yml` verifies the published npm version can be installed and run in a clean project (and skips cleanly if the version is not on npm).
102
+ Stylelint 16 and 17. Node 18.18 or newer for Stylelint 16, Node 20.19 or newer for Stylelint 17. CommonJS and ESM entry points, TypeScript declarations for every export. The CI matrix runs Node 18, 20 and 22 against Stylelint 16.0.0, 16.x and 17.x.
825
103
 
826
- ## Support and Bug Reports
104
+ ## Contributing and support
827
105
 
828
- - Open an issue: <https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/issues>
829
- - Security reports and direct contact: `hello@petrilahdelma.com`
106
+ Development setup, semver policy, benchmarking and the release process are in [CONTRIBUTING.md](CONTRIBUTING.md). Bugs and feature requests: [GitHub issues](https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/issues). Security: see [SECURITY.md](SECURITY.md).
830
107
 
831
108
  ## License
832
109
 
833
- MIT. See [`LICENSE`](./LICENSE).
110
+ MIT. See [LICENSE](./LICENSE).