← standby.design

Design Lint

Rules written as text do not stop an agent from breaking them. standby-design-lint checks the code against the standby.design design rules. Each message names the violation, the fix and the rule, so the agent can correct it right away.

Free and open source (MIT). It runs on your machine and in your own CI.

Set up and update

Run this in the root folder of the project, where package.json is. It needs Node 20.19 or newer.

npx standby-design-lint@latest init

The command installs the package as a dev dependency and sets up these parts:

Run the same command again to update. It never adds an entry twice. Hooks from other tools and your own text in CLAUDE.md and AGENTS.md stay as they are. The workflow file and the skill are written anew on each run, so local changes to these two files are lost.

With the standby.design MCP server connected, you can tell the agent “Get the latest standby update” (German: „Hol Dir das Update“). The agent calls the tool update_project, which returns the steps. Then it runs init.

GitHub only accepts the workflow file from a token with the workflow scope. If the push is rejected, run this and push again:

gh auth refresh -s workflow

How strict

StageWhenWhat happens
Claude Code hookAfter each editChecks the lines the agent just wrote. The agent gets the violations back and fixes them in the same session.
Pre-commitBefore each commitChecks the staged lines and prints a warning. The commit goes through.
Pull requestOn each pull requestChecks the lines added since the target branch. New violations fail the check.
Visual reviewOnce per finished screenTakes screenshots, counts text styles and hands both to a fresh review subagent.

The hook, the pre-commit hook and the CI check only look at new or changed lines. A project with existing violations does not turn red all at once. When the agent writes a whole file, the hook checks the whole file. To list every violation in a folder, run npx standby-design-lint src.

What it checks

Eight lint rules. Each one enforces a design rule and links to its page. The rule pages are in German. In ESLint the rule ids carry the prefix standby/.

RuleFindsDesign rule
no-raw-valuesColors, sizes and fonts set by hand, Tailwind palette colors: bg-[#fff], p-[13px], bg-blue-500„Werte kommen aus Tokens, nie aus der Hand“
no-inline-styleFixed values in inline styles: style={{ padding: 16 }}, StyleSheet.create„Utility-Klassen statt Inline-Styles“
no-state-classes-in-viewsHover, focus and active styles outside the component folder: hover:bg-muted„Verhalten und Aussehen werden nicht in der Ansicht nachgebaut“
no-transition-allTransitions on every property: transition-all, transition: all„Bewegung hat einen Wert und hält nichts auf“
no-opacity-on-semantic-colorsOpacity modifiers on semantic colors: bg-primary/10„Keine Deckkraft-Modifier auf semantischen Farben“
no-space-utilitiesSpacing through margins on the children: space-y-4, last:mb-0„Abstand über gap, nicht über Margins am Kind“
no-disabled-by-validityButtons locked until a form is valid: disabled={!isValid}„Ein Button ist nie gesperrt“
no-click-on-non-interactiveClick handlers on elements that are not controls: <div onClick>„Name und Zustand stehen im Code“

The lint checks .js, .jsx, .ts, .tsx and .mjs files. It finds classes in the attributes className, class and tw, and in the helpers cn(), clsx(), classnames(), cx(), cva(), twMerge(), twJoin() and tv(). The messages are in German.

Exceptions

Some hits are on purpose, for example a brand color fixed by the logo guideline. Put an ESLint disable comment with a reason on the line before:

const logo = cn(
  // eslint-disable-next-line standby/no-raw-values -- brand color from the logo guideline
  'bg-[#ff2d9c]',
);

Between JSX elements, use the block form {/* eslint-disable-next-line … -- reason */}. The reason after -- tells the next reader why the rule does not apply here. The comment covers the next line only. A comment that suppresses nothing counts as a violation and fails the pull request check.

If the project runs its own ESLint, add recommended() to its config (see Settings). Otherwise that ESLint reports the disable comment as an unknown rule.

Visual review

The lint reads code line by line. Some rules only show in the rendered screen. The review command needs Playwright once:

npm i -D playwright && npx playwright install --only-shell chromium
npx standby-design-lint review http://localhost:5173/settings

It saves full-page screenshots at 1280 and 375 pixels wide and a report.md in .design-review/<screen>/. The report counts the text styles on the screen. A text style is one combination of font, size, weight, color and letter case. The review checks the count against the rule „Jeder Textstil hat eine Rolle“. The rule allows 4 to 7 styles per view. The default is 5. More than 7 is a violation, more than 5 a hint.

The report also flags font sizes outside the type scale of the tokens and styles without a role while other styles have one. It flags two styles one scale step apart that differ in nothing else, and a second font family that carries more than one role.

Before the agent reports a screen as done, it follows the Claude Code skill design-review. The agent starts a fresh subagent that does not know how the screen was built. The subagent gets the screenshots, the report and a checklist and returns at most ten findings. The agent fixes them and runs the review once more. After two rounds it stops and shows you what is still open, with the screenshot.

Text roles

The review finds the role of a text through the class text-role-<role> or the attribute data-text-role. The classes come from the export of the Type tool, as CSS classes, Tailwind v4 @utility rules and design tokens.

RoleClassType level
Title 1text-role-title-1h1
Title 2text-role-title-2h3
Title 3text-role-title-3h5
Figuretext-role-figureh2, regular weight, tabular numbers
Bodytext-role-bodybody-m
Labeltext-role-labelbody-s, weight 500
Metatext-role-metacaption

The three title levels sit two steps apart on the type scale.

Settings

init adds this block if package.json has none. A second run keeps your settings. All keys are optional, the ignores entry is an example.

"standbyDesignLint": {
  "componentPaths": ["/components/ui/"],
  "allowedColors": [],
  "ignores": ["src/legacy/**"]
}

If the project already has an ESLint config, add the rules there:

import { recommended } from 'standby-design-lint';
export default [recommended({ componentPaths: ['/components/ui/'] })];

Other agents

The hook runs only in Claude Code. Cursor, Codex and other agents read the rules block in AGENTS.md. Their violations show up in the pre-commit warning and fail the pull request check. They can run the review command directly. If the project has no CLAUDE.md, init creates one that imports AGENTS.md.

Privacy

The lint and init run locally and send no code or project data anywhere. The only network access of init is the package download from npm. The review opens the URL you pass in a local browser.

Links