Skip to content

design-scale-value

Field Value
Tool ESLint
Enable via design() factory
Rule ID house-rules/design-scale-value
Type suggestion
Autofix no
Source packages/rules/src/design-scale-value.mjs
Docs packages/rules/docs/design-scale-value.md

CSS values off a fixed scale.

Require values of chosen properties to come from a fixed scale or from a token variable.

  • offScale: {{property}} “{{value}}” has values off the scale: {{parts}}. Allowed: {{allowed}}.
  • needsVar: {{property}} “{{value}}”: every layer must use var({{prefix}}…).
  • needsVarOrScale: {{property}} “{{value}}”: every layer must use var({{prefix}}…) or be one of: {{allowed}}.
[
[]
]

From packages/rules/docs/design-scale-value.md.

Rule ID: house-rules/design-scale-value

The rule runs on the @eslint/css language. For the properties you list, it requires each value to come from a fixed scale or from a token variable. It does nothing until you configure it. Use the @house-rules/rules/design factory to enable it.

design({
tokenFiles: ["apps/web/src/styles/hosti.css"],
rules: {
"design-scale-value": [
{ property: "^border(-.+)?-radius$", allowed: ["2px", "3px", "4px", "6px", "8px", "999px"] },
{ property: "^(box|text)-shadow$", allowed: ["none"] },
{ property: "^(transition|animation)(-duration)?$", allowed: ["none"], requireVar: "--t" },
],
},
});
a { border-radius: 6px; } /* passes */
a { border-radius: 2px 2px 0 0; } /* reported: 0 is off the scale */
a { transition: color var(--t) ease; } /* passes */
a { transition: opacity 420ms ease-out; } /* reported: no var(--t…) */

The single option is an array of entries:

type Options = [
Array<{
property: string; // a regular expression, tested against the property name
allowed?: string[]; // values on the scale
requireVar?: string; // a custom-property prefix, starting with "--"
}>,
];

Each entry needs allowed, requireVar, or both. property is not anchored, so write ^ and $ yourself. The first entry whose property matches a declaration applies; later entries are ignored for it. Custom-property declarations are never checked, because they define tokens.

A value always passes when it is a CSS-wide keyword (inherit, initial, unset, revert, revert-layer) or appears whole in allowed. Comparison ignores case and collapses whitespace.

The value is split on spaces, slashes and commas outside parentheses. Each part must be in allowed, or use any var(). So border-radius: var(--radius) 4px and calc(var(--radius) - 2px) pass when 4px is allowed.

Message: border-radius "2px 2px 0 0" has values off the scale: 0. Allowed: 2px, 3px, ….

The value is split into comma-separated layers, such as the transitions in a transition list. Each layer must be in allowed or use a var() whose name starts with the prefix. requireVar: "--t" accepts var(--t) and var(--t-slow). It also accepts var(--text), since this is a prefix match.

Message: transition "opacity 420ms ease-out": every layer must use var(--t…). When allowed is also set, the message lists it.

transition: none has no token. Add "none" to allowed if your code uses it, as in the example above.

The rule reports once per declaration, over the whole value.

  • 0 is a value like any other. Add "0" to allowed when zero radius is on your scale.
  • The rule does not check that a var() it accepts exists. design-no-unknown-token does that.
  • The rule has no autofix.

All rules · ESLint rules