Skip to content
onsystemv0.6.0

Rule

no-hardcoded-spacing

warning by default

Padding, margin and gap must use the spacing scale, not arbitrary px/rem values.

What it catches
Arbitrary padding, margin and gap (p-[13px], style={{ marginTop: 6 }})
Why it matters
Off-scale padding and gaps make layouts drift a pixel at a time, and an arbitrary value hides which step was meant. When the value is on the scale, the fix is just its name. A warning by default, because a deliberate one-off is sometimes right.

Example

What check_ui returned for this code against the demo design system when the site was built, from version 0.6.0.

What the agent wrote
<div className="flex items-center gap-[6px] px-[18px] py-[13px]">  <Badge>Beta</Badge></div>
After the fixthe rule’s own fixes
<div className="flex items-center gap-1.5 px-4.5 py-3">  <Badge>Beta</Badge></div>
check_ui
  1. warning1:35fix included

    gap-[6px] is 6px, which is on the spacing scale: use gap-1.5.

  2. warning1:45fix included

    px-[18px] is 18px, which is on the spacing scale: use px-4.5.

  3. warning1:55fix included

    Hardcoded spacing py-[13px] (13px) is off the scale. Nearest: py-3 (12px).

How the fix is chosen

Nearest step on the spacing scale, keeping the sign; with Tailwind v4, any whole or half step of --spacing (p-3, p-4.5, p-13) or p-px.

Every finding in the example carries its fix as text edits, which the agent applies, and so does eslint --fix. The code after the fix is those edits, applied.

Color matches under ΔE 0.02 count as the same color; under 0.1 the fix is offered; beyond that the message names the nearest token but leaves the choice to the agent. A fix is only offered for a token of the same hue (within 15°), or a gray for a gray, whether the color is a palette class, an arbitrary value or a literal: a pale color counts as a hue by its chroma relative to its lightness, so bg-[#fef2f2] is never swapped for a light gray that happens to be close. Status colors (destructive, warning, success, info and their foregrounds) are only offered for nearly the same hue (within 8°), never for a gray, so a brand-orange button does not become bg-destructive. Among the tokens that qualify, the one made for the utility wins: foreground and muted-foreground for text-*, fill-* and stroke-*; surfaces such as muted for bg-*; border, input and ring for border-* and ring-*. sidebar-* and chart-* tokens are only suggested in a sidebar or a chart (by file, enclosing component or classes), and so is any other family that at most one design-system file uses, such as syntax-highlighting colors or one screen's background, outside that file's components; dark: classes are compared with dark-mode values. Likewise, a spacing or radius step that is off by more than half the value (and more than 4px) is suggested but not auto-fixed. Rules that need tokens are skipped when the design system defines none of that category; a stylesheet that imports tailwindcss brings Tailwind's default spacing unit and radius scale, unless the theme resets that namespace (--spacing-*: initial, --radius-*: initial), in which case only the project's own steps are suggested. Syntax errors are reported as syntax, and so is code nested too deeply for TypeScript's parser (thousands of levels), which is then not checked.

Options

Its default severity is "warn". Set it to "off", "warn" or "error" in onsystem.config.json, or as onsystem/no-hardcoded-spacing in the ESLint plugin.

Values to accept anyway, as the class or the length: ["warn", { "allow": ["px-[18px]"] }].

onsystem.config.json
{  "rules": {    "no-hardcoded-spacing": "error"  }}

The config file covers the rest, including overrides for folders.

Suppressing a finding

When a finding is intended, a comment turns the rule off for the next line and says why after --. Checked when the site was built: the comment hides the finding on the line below it, and the others stay.

With the comment
// onsystem-disable-next-line no-hardcoded-spacing -- why this one is intended<div className="flex items-center gap-[6px] px-[18px] py-[13px]">  <Badge>Beta</Badge></div>

onsystem-disable-next-line covers the next line, onsystem-disable-line its own, and /* onsystem-disable */ the rest of the file (at the top, the whole file). Without rule names they turn every rule off. A misspelled rule name is reported as a suppression warning, with the closest one, and suppresses nothing; syntax errors cannot be suppressed. For whole folders or kinds of files, such as legacy screens or generated code, use overrides in the config.