In a design system, why do teams lint against raw color and spacing values in product code, and what should the rule suggest instead?
answer
- catch it while typing
- literals ignore themes
- one value, several roles
- suggest by role, not value
- allowlist the true exceptions
basics
~20 sRaw values bypass tokens, so themes, modes and system updates skip them. A lint rule catches them as code is written and suggests the token for that role — not auto-applied, since one value can serve several roles.
solid answer
~50 sA literal color code or spacing number in product code is invisible to the design system: it will not change in dark or high-contrast mode, will not follow a rebrand, and turns every future token change into a manual search. Review catches some of these, but inconsistently and late. A lint rule catches them **at authoring time and in CI**, on every change, the same way for every team. A good rule does more than say *no*: it suggests the **semantic token** for the role — text, border, surface, spacing step — and names the documentation. It should **suggest rather than auto-replace**, because the same value often belongs to several tokens: one gray might be both *subtle text* and *default border*, and picking by value alone silently encodes the wrong role. A small allowlist covers the genuine exceptions, such as zero.
code
pseudocode · 11 linesrule no-raw-style-values:
for each style value in the changed files:
if its property is color, spacing, radius, type size or elevation
and the value is a literal
and the value is not on the allowlist:
role = role implied by the property
candidates = tokens for that role whose value matches
report "Use a design token, not a raw value"
suggest candidates if any, else the tokens for that role
link the guidance page for that role
autofix only if exactly one candidatego deeper
Recall why raw values are a problem — themes, modes and system changes skip them — and that the rule points you to the token for the role you are styling.
Explain why suggestions should be by role rather than by value, why autofix is risky when one value maps to several tokens, and what belongs on the allowlist.
Show how you would write rule messages that teach the system, handle data-driven values such as customer brand colors, and apply the same check on web and native code.
Weigh strict enforcement against team autonomy, and decide which style decisions the system should own centrally versus leave to product teams.
## What the rule protects A design system expresses color, spacing, radius and type through **design tokens**: named values such as a subtle-text color or a medium spacing step. Components and product code reference the names; the system decides the values, and themes or modes remap them. A **raw value** — a literal color code or a bare spacing number written straight into product code — bypasses all of that: - **Themes and modes skip it.** A literal is correct in one theme only. Dark mode, a high-contrast mode or a white-label brand remaps every token around it and leaves the literal behind. - **System changes miss it.** When the system adjusts its error color for contrast, a hardcoded copy keeps the old one. - **It spreads.** The next engineer copies the nearby literal, and one exception becomes a pattern. - **It is hard to find later.** Searching for every place a value was used, across dozens of repositories, is slow and unreliable. ## Why a lint rule rather than review alone Code review catches raw values sometimes, depending on who reviews and how busy they are. A lint rule catches them **every time, for every team, at the moment the value is typed** — in the editor, then again in CI. Review attention is freed for questions a rule cannot answer: is this the right component, is this token's role right here? The rule is the floor; review is the judgement above it. The same rule typically runs in three places: - **In the editor**, as the engineer types, where fixing costs seconds. - **In CI**, on every change, so nothing depends on local setup. - **Across repositories**, as a count per team, so the system team can see where literals concentrate and which tokens are missing. ## What the rule should suggest The message matters as much as the check. Compare: | Rule message | Effect | |---|---| | Raw values are not allowed | Engineer guesses a token, or finds a way around the rule | | Use a token; nearest match by value: two candidates | Engineer picks one, possibly the wrong role | | Use a token for this role; for text on a surface consider subtle text or default text; see the color guidance page | Engineer picks by meaning, learns the system | Suggest **by role**, not only by value. The same resolved value frequently belongs to more than one token: a mid gray may be both the subtle-text color and the default-border color in the light theme, and differ between them in dark mode. Replacing by value alone picks one arbitrarily and bakes the wrong role in, which surfaces as a bug only when a mode changes. That is why many teams make this rule **suggest** rather than **autofix**, or autofix only when exactly one token matches and the property implies its role. ## What the rule checks, and what it allows The rule looks at style positions — color, spacing, radius, type size, elevation — and flags literal values there. Its **allowlist** covers values with no token meaning: 1. Zero, which means *none* rather than a spacing step. 2. Values that follow from the platform or content rather than the system, such as sizing to an image's own dimensions. 3. Explicit, reasoned exceptions, handled through a suppression the team can see and review. The same idea applies on native mobile: literal colors or spacing in a screen's layout code bypass the token output generated for that platform, and a platform-appropriate lint rule flags them the same way. ## An example in an e-signature product The signing page highlights each field the signer must complete. An engineer writes a literal yellow for the highlight and a literal spacing number around the field. The rule flags both, suggests the highlight-surface token and the small spacing step, and links to the guidance. In high-contrast mode the highlight now follows the system's high-contrast palette instead of staying a pale yellow that a low-vision signer cannot see. One exception is real: the **sender's brand color**, which the sending company configures in its account and which the signing page shows in its header. That value is **data**, not a design decision, so it is read from configuration and passed through a sanctioned mechanism rather than written as a literal — the rule has nothing to flag.
- Why not autofix every raw value to the token with the same value?Because one value often belongs to several tokens with different roles. A gray that is both the subtle-text and the border color in one theme may diverge in another. An autofix by value picks one arbitrarily and encodes the wrong role, which only shows when a mode or the system changes. Suggest by role, and autofix only when exactly one candidate fits.
- How should the rule treat a value that comes from customer configuration, such as a sender's brand color?It is data, not a design decision, so it should not be a literal at all: read it from configuration and apply it through the mechanism the system provides for runtime brand values. Then there is nothing to flag. If code writes it as a literal, that is a real finding, not a false positive.
saying these in an interview costs you the question
- Raw values are fine as long as they match the token's current value.
- Code review alone reliably catches raw values, so lint is redundant.
- The rule should autofix every literal to whichever token has that value.
- A lint rule that only says no is enough; engineers will find the token.
- Every numeric value in styling code, including zero, must be a token.