DEV Community

Petri Lahdelma
Petri Lahdelma

Posted on

Enforcing Your Spacing Standards with Rhythmguard (A Custom Stylelint Plugin)

CSS drift is real.

Even on teams with a spacing scale and token system, random values still creep in:

  • 13px margins
  • 18px translate animations
  • raw literals where tokens should be used

That drift chips away at consistency and makes UI behavior harder to reason about over time.

I built Rhythmguard to stop that at lint time.

What Rhythmguard enforces

stylelint-plugin-rhythmguard focuses on three rules:

  1. rhythmguard/use-scale

    Checks spacing values against your approved scale and can autofix to the nearest allowed value.

  2. rhythmguard/prefer-token

    Enforces token usage over raw spacing literals and can autofix with an explicit tokenMap.

  3. rhythmguard/no-offscale-transform

    Applies scale rules to translation motion values (translateY, etc.) so motion rhythm stays aligned with layout rhythm.

Install and quick start

npm install --save-dev stylelint stylelint-plugin-rhythmguard
Enter fullscreen mode Exit fullscreen mode
{
  "plugins": ["stylelint-plugin-rhythmguard"],
  "extends": ["stylelint-plugin-rhythmguard/configs/strict"]
}
Enter fullscreen mode Exit fullscreen mode

You also get recommended and tailwind shared configs depending on how aggressively you want to enforce standards.

Why a plugin instead of guidelines

Docs and Figma tokens are necessary, but they’re not enforcement.

What teams actually need is:

  • fast feedback in CI and local linting
  • deterministic autofix for migration work
  • guardrails that encode system rules, not preferences

That’s the gap Rhythmguard fills.

How the rule logic works

At a high level, each rule does this:

  1. Parse declaration values with postcss-value-parser.
  2. Target spacing-sensitive properties (margin, padding, gap, inset, scroll spacing, transform translate functions, etc.).
  3. Normalize units (rem, em, px) to a comparable numeric scale.
  4. Report violations via Stylelint’s reporting API.
  5. Apply a safe fix only when deterministic.

A simplified pattern looks like this:

const stylelint = require("stylelint");
const valueParser = require("postcss-value-parser");

const ruleName = "rhythmguard/use-scale";

module.exports = stylelint.createPlugin(ruleName, (enabled, opts = {}) => {
  return (root, result) => {
    if (!enabled) return;

    root.walkDecls((decl) => {
      if (!isSpacingProperty(decl.prop)) return;

      const ast = valueParser(decl.value);
      walkLengthNodes(ast, (node) => {
        const px = toPx(node.value, opts.baseFontSize ?? 16);
        if (onScale(px, opts.scale)) return;

        stylelint.utils.report({
          ruleName,
          result,
          node: decl,
          message: `Off-scale value "${node.value}"`,
          fix: opts.fixToScale ? () => (node.value = nearestScaleValue(px, opts.scale)) : undefined
        });
      });

      decl.value = ast.toString();
    });
  };
});
Enter fullscreen mode Exit fullscreen mode

The key is the fix strategy: only deterministic fixes. No guessing.

Real example: before and after

In the demo repo, this input is intentionally broken:

.card {
  margin: 13px;
  padding: 18px 22px;
  gap: 12px;
  transform: translateY(18px) scale(1);
}
Enter fullscreen mode Exit fullscreen mode

With scale + token rules and an explicit token map, autofix converges it to consistent, tokenized output.

That gives you two wins at once:

  • spacing values become scale-safe
  • literal values migrate toward design tokens

Tailwind note (important)

Rhythmguard enforces what Stylelint can parse: CSS declarations.

It does not lint Tailwind class strings like class="p-[13px] translate-y-[18px]".
For full Tailwind governance, pair Rhythmguard with Tailwind-aware ESLint/class tooling.

Rollout strategy that works

If you’re introducing this to an existing codebase, do it in phases:

  1. Enable rhythmguard/use-scale with autofix.
  2. Add rhythmguard/no-offscale-transform.
  3. Introduce rhythmguard/prefer-token in migration mode.
  4. Lock into strict token enforcement once your token map is complete.

This keeps the migration practical and avoids noisy, non-actionable linting.

Closing

Spacing consistency is one of those things teams care about until deadlines hit.

Rhythmguard turns that concern into enforceable, testable rules so standards survive real delivery pressure.

Sources:

Top comments (0)