Rule
no-hardcoded-spacing
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.
<div className="flex items-center gap-[6px] px-[18px] py-[13px]"> <Badge>Beta</Badge></div><div className="flex items-center gap-1.5 px-4.5 py-3"> <Badge>Beta</Badge></div>warning1:35fix included
gap-[6px]is 6px, which is on the spacing scale: usegap-1.5.warning1:45fix included
px-[18px]is 18px, which is on the spacing scale: usepx-4.5.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]"] }].
{ "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.
// 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.