The on-system lint enforces tokens, primitives, and house utilities in consumer codebases, the same engine that grades the design system itself. One plugin, one CLI, zero raw colors.
01, ESLint plugin
@byronwade/eslint-plugin-ui ships a flat-config-ready recommended preset that enables all five detectors. Works with ESLint 9+ and the flat-config format. It pairs with the AI rule — the rule keeps agents generating on-system code, the lint catches anything that still drifts.
install
eslint.config.js
Options
| option | default | description |
|---|---|---|
| maxColorDistance | 10 | Maximum ΔE distance before a raw color is flagged as "too far from any token". Lower values are stricter. |
| offSystemComponents | ["button", "input", "select"] | Native HTML elements for which a design-system primitive exists. Override to expand or reduce the set. |
02, CLI
byronwade-lint runs the same detectors outside of ESLint, useful in CI, pre-commit hooks, and editors that don't speak ESLint flat config yet. The --fix flag auto-applies safe codemods where a unique nearest token can be found.
check
auto-fix
03, What it catches
Five detectors cover the most common ways consumer code drifts off-system. Each maps a violation to a specific token or house utility from the foundation scale so the fix is always clear.
Raw color
Hex, rgb(), hsl(), and named colors are replaced with their nearest semantic token (e.g. text-foreground, bg-brand, bg-destructive). The maximum allowed distance is configurable via maxColorDistance.
Off-token arbitrary spacing & radius
Arbitrary Tailwind values like p-[14px] or rounded-[6px] are flagged. Use the spacing and radius steps from the foundation scale instead.
Hand-rolled gradients, grids, and glows
Custom gradient or grid CSS that duplicates a house utility (glow-brand, bg-grid, text-gradient-brand, mask-fade-x) triggers a warning to use the utility instead.
Raw native elements where a primitive exists
Using a bare <button>, <input>, or <select> where a design-system primitive covers the same role produces a warning. Controlled via the offSystemComponents option.
Bold weight on headings
font-semibold and font-bold on h1–h6 elements (or heading-role components) are flagged, the editorial-typography DNA requires font-medium or font-normal.
04, Before → after
A typical off-system snippet leans on hardcoded colors, arbitrary spacing, and a bare native element. The lint rewrites each line to the nearest token, foundation step, and primitive.
before · off-system
after · on-system
bg-[#16a34a] resolves to bg-brand (baked into the primitive), the hardcoded white maps to the primitive's text-primary-foreground, and the arbitrary radius and padding snap to the radius and spacing steps.
05, What autofix can & can't do
--fix only applies a codemod when the correct replacement is unambiguous. Anything that needs a human judgment call is reported but left untouched.
Auto-applied
- Raw colors within
maxColorDistanceof exactly one token - Arbitrary spacing and radius that map cleanly to a foundation step
- Hand-rolled CSS that has a one-to-one house-utility equivalent (
glow-brand,bg-grid)
Reported only
- Colors equidistant from two tokens — the intent is ambiguous
- Bare
<button>/<input>swaps that need a primitive import and prop mapping - Bold headings — the fix depends on the intended visual hierarchy
06, CI & editor
Run the lint as a gate in CI so off-system code never lands, and wire the ESLint plugin into your editor for inline feedback while you type. Both reuse the same registry install and config you already have.
.github/workflows/lint.yml
.vscode/settings.json
With the ESLint VS Code extension installed, off-system code is underlined inline and Fix all auto-fixable problems applies the same codemods as --fix.