stylelint-plugin-rhythmguard 2.0.0 → 2.1.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,28 @@
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:
18
-
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
16
+ ## Install
25
17
 
26
18
  ```bash
27
19
  npm install --save-dev stylelint stylelint-plugin-rhythmguard
28
20
  ```
29
21
 
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
- ];
54
- ```
55
-
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
180
-
181
- ```bash
182
- npm install --save-dev stylelint stylelint-plugin-rhythmguard
183
- ```
184
-
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
22
  ```json
194
23
  {
195
24
  "extends": ["stylelint-plugin-rhythmguard/configs/recommended"]
196
25
  }
197
26
  ```
198
27
 
199
- ## Quick Start
200
-
201
- ### Tailwind config
28
+ 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
29
 
203
30
  ```json
204
31
  {
@@ -206,626 +33,67 @@ npm install --save-dev stylelint-plugin-rhythmguard
206
33
  }
207
34
  ```
208
35
 
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
- - 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)
295
- - Distribution submissions to Stylelint discovery surfaces: [`docs/DISTRIBUTION.md`](https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/blob/main/docs/DISTRIBUTION.md)
296
-
297
- ### Full custom setup
298
-
299
- ```json
300
- {
301
- "plugins": ["stylelint-plugin-rhythmguard"],
302
- "rules": {
303
- "rhythmguard/use-scale": [
304
- true,
305
- {
306
- "preset": "rhythmic-4",
307
- "propertyGroups": ["spacing", "radius"],
308
- "propertyScales": {
309
- "font-size": [12, 14, 16, 20, 24]
310
- },
311
- "units": ["px", "rem", "em"],
312
- "unitStrategy": "convert",
313
- "baseFontSize": 16,
314
- "tokenPattern": "^--space-",
315
- "tokenFunctions": ["var", "theme", "token"],
316
- "allowNegative": true,
317
- "allowPercentages": true,
318
- "fixToScale": true,
319
- "enforceInsideMathFunctions": true,
320
- "mathFunctionArguments": {
321
- "clamp": [1, 3]
322
- }
323
- }
324
- ],
325
- "rhythmguard/prefer-token": [
326
- true,
327
- {
328
- "tokenPattern": "^--space-",
329
- "allowNumericScale": false,
330
- "tokenMapFromCssCustomProperties": true,
331
- "tokenMapFromTailwindSpacing": true,
332
- "tailwindConfigPath": "./tailwind.config.mjs",
333
- "tokenMap": {
334
- "4px": "var(--space-1)",
335
- "8px": "var(--space-2)",
336
- "12px": "var(--space-3)",
337
- "16px": "var(--space-4)"
338
- }
339
- }
340
- ],
341
- "rhythmguard/no-offscale-transform": [
342
- true,
343
- {
344
- "scale": [0, 4, 8, 12, 16, 24, 32]
345
- }
346
- ]
347
- }
348
- }
349
- ```
350
-
351
- ### Presets and custom scales
352
-
353
- Preset-based setup:
354
-
355
- ```json
356
- {
357
- "rules": {
358
- "rhythmguard/use-scale": [true, { "preset": "fibonacci" }]
359
- }
360
- }
361
- ```
362
-
363
- Custom scale setup:
364
-
365
- ```json
366
- {
367
- "rules": {
368
- "rhythmguard/use-scale": [true, { "customScale": [0, 6, 12, 18, 24, 36, 48] }]
369
- }
370
- }
371
- ```
372
-
373
- Scale resolution precedence:
374
-
375
- 1. `customScale` (highest priority)
376
- 2. `scale`
377
- 3. `preset`
378
- 4. default `rhythmic-4` scale
379
-
380
- ## Option Validation
381
-
382
- Rhythmguard validates `secondaryOptions` for each rule before linting declarations.
383
-
384
- - Unknown option names fail fast with Stylelint invalid option warnings.
385
- - Invalid option shapes fail fast (for example string vs array mismatches).
386
- - `properties` string entries are validated against supported scale-targetable CSS property names.
387
- - `propertyGroups` values are validated against built-in groups: `spacing`, `radius`, `typography`, and `size`.
388
- - Math function argument maps are validated per function (`calc`, `clamp`, `min`, `max`) and positive 1-based argument indexes.
389
-
390
- Example typo that now fails immediately:
391
-
392
- ```json
393
- {
394
- "rules": {
395
- "rhythmguard/use-scale": [true, { "sevverity": "warning" }]
396
- }
397
- }
398
- ```
399
-
400
- ## Built-in Scale Presets
401
-
402
- | Preset | Pattern | Scale |
403
- | --- | --- | --- |
404
- | `rhythmic-4` | 4pt rhythm | `[0,4,8,12,16,24,32,40,48,64]` |
405
- | `rhythmic-8` | 8pt rhythm | `[0,8,16,24,32,40,48,64,80,96]` |
406
- | `product-material-8dp` | Material 8dp baseline + 4dp increments | `[0,4,8,12,16,24,32,40,48,56,64,72,80]` |
407
- | `product-atlassian-8px` | Atlassian-like product spacing progression | `[0,2,4,6,8,12,16,20,24,32,40,48,64,80]` |
408
- | `product-carbon-2x` | Carbon 2x spacing progression | `[0,2,4,8,12,16,24,32,40,48,64,80]` |
409
- | `editorial-baseline-4` | editorial baseline rhythm at 4-unit cadence | `[0,4,8,12,16,20,24,28,32,40,48,56,64]` |
410
- | `editorial-baseline-6` | editorial baseline rhythm at 6-unit cadence | `[0,6,12,18,24,30,36,48,60,72]` |
411
- | `compact` | dense UI spacing | `[0,2,4,6,8,12,16,20,24,32]` |
412
- | `fibonacci` | Fibonacci progression | `[0,2,3,5,8,13,21,34,55,89]` |
413
- | `powers-of-two` | geometric doubling | `[0,2,4,8,16,32,64,128]` |
414
- | `golden-ratio` | ratio 1.618 | generated modular sequence |
415
- | `modular-major-second` | ratio 1.125 | generated modular sequence |
416
- | `modular-minor-third` | ratio 1.2 | generated modular sequence |
417
- | `modular-major-third` | ratio 1.25 | generated modular sequence |
418
- | `modular-augmented-fourth` | ratio 1.414 | generated modular sequence |
419
- | `modular-perfect-fourth` | ratio 1.333 | generated modular sequence |
420
- | `modular-perfect-fifth` | ratio 1.5 | generated modular sequence |
421
-
422
- Aliases:
423
-
424
- - `4pt` → `rhythmic-4`
425
- - `8pt` → `rhythmic-8`
426
- - `material` → `product-material-8dp`
427
- - `atlassian-8` → `product-atlassian-8px`
428
- - `carbon` → `product-carbon-2x`
429
- - `baseline-4` → `editorial-baseline-4`
430
- - `baseline-6` → `editorial-baseline-6`
431
- - `golden` → `golden-ratio`
432
- - `major-second` → `modular-major-second`
433
- - `minor-third` → `modular-minor-third`
434
- - `major-third` → `modular-major-third`
435
- - `augmented-fourth` → `modular-augmented-fourth`
436
- - `perfect-fourth` → `modular-perfect-fourth`
437
- - `perfect-fifth` → `modular-perfect-fifth`
438
-
439
- ### Preset Rationale
440
-
441
- - Product presets are based on widely-used design-system spacing frameworks.
442
- - Editorial presets model baseline-grid cadence used in long-form typography and column layouts.
443
- - Theory presets expose mathematically-derived modular scales from design theory and typographic proportion systems.
444
- - 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).
445
-
446
- ## Community Scale Registry
447
-
448
- Rhythmguard supports community-contributed scale presets from `scales/community/*.json`.
449
-
450
- ### Current community scales
451
-
452
- | Preset | Base | Pattern | Contributor |
453
- | --- | --- | --- | --- |
454
- | `product-decimal-10` | `10` | Decimal-friendly dashboard/product cadence | [Petri Lahdelma](https://github.com/PetriLahdelma) |
455
-
456
- ### Contribute a scale
457
-
458
- 1. Scaffold a new scale file:
459
-
460
- ```bash
461
- npm run scales:add -- --name my-team-scale --base 8 --steps 0,4,8,12,16,24,32
462
- ```
463
-
464
- 2. Validate:
465
-
466
- ```bash
467
- npm run scales:validate
468
- ```
469
-
470
- 3. Open a PR with your scale JSON.
471
-
472
- Full specification and policy: [`docs/COMMUNITY_SCALES.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/COMMUNITY_SCALES.md).
473
-
474
- If your scale is private or very niche, keep it in your project config with `customScale` instead of contributing it to the shared registry.
475
-
476
- ## Rule Details
477
-
478
- ### `rhythmguard/use-scale`
479
-
480
- Enforces spacing literals to stay on a configured numeric scale.
481
-
482
- Checks:
483
-
484
- - `margin*`, `padding*`
485
- - `gap`, `row-gap`, `column-gap`
486
- - `inset*`, `scroll-margin*`, `scroll-padding*`
487
- - `translate`, `translate-x`, `translate-y`, `translate-z`
488
- - `transform` translation functions (`translate`, `translateX`, `translateY`, `translateZ`, `translate3d`)
489
- - optional property groups:
490
- - `radius` (`border-radius*`, corner radii, `outline-offset`)
491
- - `typography` (`font-size`, `line-height`, `letter-spacing`, `word-spacing`)
492
- - `size` (`width`, `height`, min/max size, logical `inline-size`/`block-size`)
493
-
494
- Example:
495
-
496
- ```css
497
- /* ❌ Off-scale */
498
- .card {
499
- margin: 13px;
500
- transform: translateY(18px);
501
- }
502
-
503
- /* ✅ On-scale */
504
- .card {
505
- margin: 12px;
506
- transform: translateY(16px);
507
- }
508
- ```
509
-
510
- Options:
511
-
512
- | Option | Type | Default | Description |
513
- | --- | --- | --- | --- |
514
- | `preset` | `string` | `rhythmic-4` | Selects a built-in spacing scale |
515
- | `customScale` | `Array<number|string>` | `undefined` | Highest-priority custom scale override |
516
- | `scale` | `Array<number|string>` | `[0,4,8,12,16,24,32,40,48,64]` | Allowed spacing values |
517
- | `units` | `string[]` | `['px','rem','em']` | Units considered for scale enforcement |
518
- | `unitStrategy` | `'convert' \| 'exact'` | `'convert'` | `convert`: compare via px conversion (`px/rem/em`). `exact`: compare against same-unit scale values (for example `vw`, `cqi`) |
519
- | `baseFontSize` | `number` | `16` | Used for `rem`/`em` conversion |
520
- | `tokenPattern` | `string` | `^--space-` | Regex for accepted token variable names |
521
- | `tokenFunctions` | `string[]` | `['var','theme','token']` | Functions treated as tokenized values |
522
- | `allowNegative` | `boolean` | `true` | Allows negative scale values |
523
- | `allowPercentages` | `boolean` | `true` | Allows `%` values without scale checks |
524
- | `fixToScale` | `boolean` | `true` | Enables nearest-value autofix |
525
- | `enforceInsideMathFunctions` | `boolean` | `false` | Lints `calc()/clamp()/min()/max()` internals |
526
- | `mathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Restricts linting to specific 1-based argument indexes per math function |
527
- | `ignoreMathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Excludes specific 1-based argument indexes per math function |
528
- | `propertyGroups` | `Array<'spacing' \| 'radius' \| 'typography' \| 'size' \| 'motion'>` | `['spacing']` | Selects built-in property groups when `properties` is not provided |
529
- | `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`) |
530
- | `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides (supports exact names or `/regex/flags` keys; stateful `g`/`y` flags are normalized for deterministic matching) |
531
-
532
- ### `rhythmguard/prefer-token`
533
-
534
- Enforces token usage for spacing declarations. This is ideal once your token system is stable.
535
-
536
- Example:
537
-
538
- ```css
539
- /* ❌ Raw literals */
540
- .stack {
541
- gap: 12px;
542
- padding: 16px;
543
- }
544
-
545
- /* ✅ Tokenized */
546
- .stack {
547
- gap: var(--space-3);
548
- padding: var(--space-4);
549
- }
550
- ```
551
-
552
- Options:
553
-
554
- | Option | Type | Default | Description |
555
- | --- | --- | --- | --- |
556
- | `tokenPattern` | `string` | `^--space-` | Regex for accepted token variable names |
557
- | `tokenFunctions` | `string[]` | `['var','theme','token']` | Functions treated as tokenized values |
558
- | `allowNumericScale` | `boolean` | `false` | Temporary migration mode to permit on-scale literals |
559
- | `preset` | `string` | `rhythmic-4` | Selects a built-in scale used in migration mode |
560
- | `customScale` | `Array<number|string>` | `undefined` | Highest-priority custom scale override |
561
- | `scale` | `Array<number|string>` | `[0,4,8,12,16,24,32,40,48,64]` | Used when `allowNumericScale` is enabled |
562
- | `baseFontSize` | `number` | `16` | Used for scale checks with `rem`/`em` |
563
- | `unitStrategy` | `'convert' \| 'exact'` | `'convert'` | Matching strategy when `allowNumericScale` is enabled |
564
- | `units` | `string[]` | `['px','rem','em']` | Units considered for numeric scale checks |
565
- | `enforceInsideMathFunctions` | `boolean` | `false` | Lints `calc()/clamp()/min()/max()` internals |
566
- | `mathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Restricts linting to specific 1-based argument indexes per math function |
567
- | `ignoreMathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Excludes specific 1-based argument indexes per math function |
568
- | `tokenMap` | `Record<string,string>` | `{}` | Enables autofix from raw value to token |
569
- | `tokenMapFile` | `string` | `null` | JSON file path to merge additional token mappings (supports flat, Style Dictionary, and W3C DTCG formats) |
570
- | `tokenMapFromCssCustomProperties` | `boolean` | `false` | Auto-builds mappings from matching custom property declarations in the same stylesheet |
571
- | `tokenMapFromTailwindSpacing` | `boolean` | `false` | Auto-builds mappings from `theme.spacing` and `theme.extend.spacing` in Tailwind config |
572
- | `tailwindConfigPath` | `string` | `null` | Path to Tailwind config used by `tokenMapFromTailwindSpacing` (`.js`, `.cjs`, `.mjs`) |
573
- | `ignoreValues` | `string[]` | CSS global keywords + `auto` | Skips keyword literals |
574
- | `propertyGroups` | `Array<'spacing' \| 'radius' \| 'typography' \| 'size'>` | `['spacing']` | Selects built-in property groups when `properties` is not provided |
575
- | `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`) |
576
- | `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides for numeric migration mode (stateful `g`/`y` flags are normalized for deterministic matching) |
577
-
578
- ### `rhythmguard/no-offscale-transform`
579
-
580
- Specialized guardrail for motion spacing consistency in translation transforms.
581
-
582
- Example:
583
-
584
- ```css
585
- /* ❌ Off-scale motion */
586
- .toast {
587
- transform: translateY(18px) scale(1);
588
- }
589
-
590
- /* ✅ Motion on spacing scale */
591
- .toast {
592
- transform: translateY(16px) scale(1);
593
- }
594
- ```
595
-
596
- Options:
597
-
598
- `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.
599
-
600
- ### `rhythmguard/use-motion-scale`
601
-
602
- Opt-in guardrail for duration, delay, and easing rhythm.
603
-
604
- Example:
605
-
606
- ```css
607
- /* ❌ Off-scale timing + raw easing */
608
- .button {
609
- transition: opacity 175ms cubic-bezier(.2, 0, 0, 1);
610
- }
611
-
612
- /* ✅ Timing on motion scale */
613
- .button {
614
- transition: opacity 150ms var(--ease-snappy);
615
- }
616
- ```
617
-
618
- Options:
619
-
620
- | Option | Type | Default | Description |
621
- | --- | --- | --- | --- |
622
- | `durationScale` | `number[]` | `[0,75,100,150,200,300,500,700,1000]` | Allowed duration and delay values in milliseconds |
623
- | `durationUnits` | `Array<'ms' \| 's'>` | `['ms','s']` | Time units considered by the rule |
624
- | `fixToScale` | `boolean` | `true` | Autofixes simple duration/delay values to the nearest scale value |
625
- | `easingTokenMap` | `Record<string,string>` | `{}` | Optional exact replacements for raw easing functions |
626
-
627
- Tailwind class strings can use the ESLint companion rule `rhythmguard-tailwind/tailwind-class-use-motion-scale` for `duration-[...]`, `delay-[...]`, and `ease-[...]` arbitrary values.
628
-
629
- ## Tailwind CSS Integration
630
-
631
- Rhythmguard works well in Tailwind projects, but it enforces what Stylelint can parse: CSS declarations.
632
-
633
- ### What Rhythmguard covers in Tailwind projects
634
-
635
- - custom CSS in `globals.css`, `components.css`, `utilities.css`
636
- - CSS Modules (for example `*.module.css`)
637
- - declarations inside `@layer` blocks
638
-
639
- ### Tailwind v4 @theme tokens
640
-
641
- 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)`.
642
-
643
- See [`docs/TAILWIND.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/TAILWIND.md) for full setup.
644
-
645
- ### What the Stylelint layer does not cover
646
-
647
- - Tailwind class strings in templates/JSX/TSX, for example:
648
- - `class="p-4 gap-2"`
649
- - `class="p-[13px] translate-y-[18px]"`
650
-
651
- 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.
652
-
653
- ### Companion ESLint layer for class strings
654
-
655
- Rhythmguard now ships an ESLint companion export for class-string governance:
36
+ For class strings in JSX, TSX, Vue, Svelte or Astro, add the ESLint companion:
656
37
 
657
38
  ```js
658
- // eslint.config.js (flat config)
39
+ // eslint.config.js
659
40
  import rhythmguard from 'stylelint-plugin-rhythmguard/eslint';
660
41
 
661
42
  export default [
662
43
  {
663
- plugins: {
664
- 'rhythmguard-tailwind': rhythmguard,
665
- },
666
- rules: {
667
- 'rhythmguard-tailwind/tailwind-class-use-scale': ['error', { scale: [0, 4, 8, 12, 16, 24, 32] }],
668
- },
44
+ plugins: { 'rhythmguard-tailwind': rhythmguard },
45
+ rules: { 'rhythmguard-tailwind/tailwind-class-use-scale': ['error', { scale: [0, 4, 8, 12, 16, 24, 32] }] },
669
46
  },
670
47
  ];
671
48
  ```
672
49
 
673
- This rule targets arbitrary spacing utilities such as `p-[13px]`, `gap-[18px]`, `translate-x-[10px]`, and autofixes to the nearest configured scale value.
674
-
675
- #### Supported patterns
676
-
677
- The rule checks every string literal in your code, so it works automatically with common utility functions:
678
-
679
- - `cn("p-[13px]")` / `cn("p-[13px]", condition && "m-[7px]")`
680
- - `clsx("p-[13px]", "gap-[18px]")`
681
- - `twMerge("p-[13px]", otherClasses)`
682
- - `cva("base", { variants: { size: { sm: "p-[5px]" } } })`
683
- - `<div className={cn("p-[13px]")} />`
684
-
685
- No extra config needed — if the string contains an arbitrary spacing value, it gets caught and autofixed.
686
-
687
- ### Recommended stack for full Tailwind enforcement
688
-
689
- Use both layers:
690
-
691
- 1. Stylelint + Rhythmguard for CSS declaration governance.
692
- 2. Tailwind-aware class-string linting/formatting for template utility usage.
693
-
694
- Suggested setup:
695
-
696
- ```json
697
- {
698
- "extends": ["stylelint-plugin-rhythmguard/configs/tailwind"]
699
- }
700
- ```
701
-
702
- Then pair with:
703
-
704
- - `stylelint-plugin-rhythmguard/eslint` for arbitrary spacing class-string scale enforcement.
705
- - `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.
706
- - `prettier-plugin-tailwindcss` for deterministic class ordering.
707
-
708
- Detailed setup reference: [`docs/TAILWIND.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/TAILWIND.md).
709
-
710
- ### Tailwind token function support
711
-
712
- By default, `tokenFunctions` includes `theme`, so values like `theme(spacing.4)` are treated as tokenized values.
713
-
714
- This keeps CSS declaration enforcement and template class-string enforcement separated but coordinated.
715
-
716
- ## Programmatic Presets
717
-
718
- ```js
719
- const rhythmguard = require('stylelint-plugin-rhythmguard');
720
-
721
- console.log(rhythmguard.presets.listScalePresetNames());
722
- console.log(rhythmguard.presets.listCommunityScalePresetNames());
723
- console.log(rhythmguard.presets.getCommunityScaleMetadata('product-decimal-10'));
724
- console.log(rhythmguard.presets.scales['rhythmic-4']);
725
- console.log(Object.keys(rhythmguard.eslint.rules));
726
- ```
727
-
728
- ## Token File Formats
729
-
730
- The `tokenMapFile` option supports multiple JSON formats:
731
-
732
- **Flat token-to-value:**
733
-
734
- ```json
735
- { "--spacing-4": "16px", "--spacing-3": "12px" }
736
- ```
737
-
738
- **Style Dictionary:**
739
-
740
- ```json
741
- { "--spacing-4": { "value": "16px" } }
742
- ```
743
-
744
- **W3C DTCG (Design Token Community Group):**
745
-
746
- ```json
747
- {
748
- "spacing": {
749
- "4": { "$value": "16px", "$type": "dimension" },
750
- "2": { "$value": "8px", "$type": "dimension" }
751
- }
752
- }
753
- ```
754
-
755
- 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.
756
-
757
- ## Autofix Philosophy
758
-
759
- Rhythmguard only applies deterministic fixes:
50
+ ## Rules
760
51
 
761
- - nearest scale value for numeric off-scale literals
762
- - explicit `tokenMap` replacements for token migration
763
-
764
- It will not guess token mappings without your map.
765
-
766
- ## Compatibility
767
-
768
- - Stylelint: `^16.0.0 || ^17.0.0`
769
- - Node.js: `>=18.18.0`
770
- - Module format: dual `require` + `import` entry points (CommonJS + ESM wrappers)
771
- - 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.
52
+ | Rule | What it reports | Fix |
53
+ | --- | --- | --- |
54
+ | [`rhythmguard/use-scale`](docs/rules/use-scale.md) | Length values off the configured scale | Nearest scale value |
55
+ | [`rhythmguard/prefer-token`](docs/rules/prefer-token.md) | Raw literals where a design token exists | Token from your map |
56
+ | [`rhythmguard/no-offscale-transform`](docs/rules/no-offscale-transform.md) | Off-scale `translate*` offsets | Nearest scale value |
57
+ | [`rhythmguard/use-motion-scale`](docs/rules/use-motion-scale.md) | Off-scale durations and raw easing curves. Opt-in, experimental | Nearest duration |
58
+ | [`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 |
59
+ | [`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 |
772
60
 
773
- ## Development
61
+ Every rule validates its options up front. Unknown option names and wrong shapes are reported, never ignored.
774
62
 
775
- ```bash
776
- npm install
777
- npm run lint
778
- npm test
779
- npm run test:coverage
780
- ```
63
+ ## Configs
781
64
 
782
- ## Performance Benchmarking
65
+ `recommended`, `strict`, `tailwind`, `react-tailwind`, `expanded`, `logical`, `migration`, `motion`. 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).
783
66
 
784
- Compare runtime against `stylelint-scales` on a deterministic spacing corpus:
67
+ ## Audit before you enforce
785
68
 
786
69
  ```bash
787
- npm run bench:perf
788
- ```
789
-
790
- Benchmark with autofix enabled:
791
-
792
- ```bash
793
- npm run bench:perf:fix
70
+ npx rhythmguard audit ./src --format markdown
71
+ npx rhythmguard audit ./src --write-baseline
72
+ npx rhythmguard audit ./src --since-baseline --fail-on-new-drift
73
+ npx rhythmguard audit ./src --format github
794
74
  ```
795
75
 
796
- Detailed methodology and custom args are documented in [`docs/BENCHMARKING.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/BENCHMARKING.md).
797
-
798
- ## Article
76
+ 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).
799
77
 
800
- - Dev.to: [Enforcing your spacing standards with Rhythmguard](https://dev.to/petrilahdelma/enforcing-your-spacing-standards-with-rhythmguard-a-custom-stylelint-plugin-1ojj)
78
+ `npx rhythmguard init` writes a starter config for your stack. `npx rhythmguard doctor` checks the setup.
801
79
 
802
- ## Used by and Community Examples
80
+ ## Guides
803
81
 
804
- Public codebases currently used for production migration examples:
82
+ - [Tailwind integration](docs/TAILWIND.md), including v4 `@theme` tokens and what each layer covers
83
+ - [Framework setup](docs/FRAMEWORKS.md) for Vue, Lit, Astro and SvelteKit
84
+ - [Comparison with adjacent plugins](docs/COMPARISON.md) and migration recipes
85
+ - [Real before/after excerpts](docs/ADOPTION_DIFFS.md) from public codebases
86
+ - [Product direction](docs/STRATEGY_2026-09.md)
87
+ - Browser playground: [petrilahdelma.github.io/stylelint-plugin-rhythmguard](https://petrilahdelma.github.io/stylelint-plugin-rhythmguard/)
805
88
 
806
- - [PetriLahdelma/digitaltableteur-nextjs](https://github.com/PetriLahdelma/digitaltableteur-nextjs)
807
- - [PetriLahdelma/digitaltableteur](https://github.com/PetriLahdelma/digitaltableteur)
808
-
809
- Want your team listed here?
810
-
811
- 1. Open an issue with `used-by` in the title.
812
- 2. Include one before/after diff and your Rhythmguard config.
813
- 3. Add migration notes (false positives, rules enabled, rollout phase).
814
-
815
- ## Release Workflow
89
+ ## Compatibility
816
90
 
817
- 1. Create a GitHub release.
818
- 2. `release.yml` runs the Node/Stylelint matrix validation.
819
- 3. A tarball smoke test validates package exports and install behavior.
820
- 4. If `NPM_TOKEN` is configured in repository secrets, the package is published to npm with provenance (`npm publish --provenance`).
821
- 5. If `NPM_TOKEN` is not configured, publish is skipped with an explicit workflow notice.
822
- 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).
91
+ 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.
823
92
 
824
- ## Support and Bug Reports
93
+ ## Contributing and support
825
94
 
826
- - Open an issue: <https://github.com/petrilahdelma/stylelint-plugin-rhythmguard/issues>
827
- - Security reports and direct contact: `hello@petrilahdelma.com`
95
+ 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).
828
96
 
829
97
  ## License
830
98
 
831
- MIT. See [`LICENSE`](./LICENSE).
99
+ MIT. See [LICENSE](./LICENSE).