Designrail CLI reference
The designrail command line tool runs the same engine as the Designrail extension through your locally installed Chrome or Edge. Use it to turn a website's rendered design into a DESIGN.md and agent rule files, to score a running build against that file, to lint source code for values that bypass the tokens, and to serve the design system to coding agents over MCP. Nothing is uploaded anywhere.
Install
The package is designrail on npm and needs Node.js 20 or later. Run it without installing:
pnpm dlx designrail --help
Or add it to a project so the version is pinned in your lockfile, then run it with pnpm exec:
pnpm add -D designrail
pnpm exec designrail lint-code src
scan, check and the MCP check_url tool need Chrome or Edge (see Browser and environment below). lint-code and the other MCP tools only read files.
Top-level commands:
| Command | What it does |
|---|---|
designrail scan <url> |
Extract tokens and write DESIGN.md and SKILL.md, or the full project layout with --install |
designrail check <url> |
Render a running build and score it against DESIGN.md |
designrail lint-code [paths…] |
Find raw colours, Tailwind palette classes, off-grid spacing and unknown fonts in source |
designrail mcp |
Serve the design system to coding agents over MCP (stdio) |
designrail --help, -h, help |
Print the usage text for every command |
designrail <command> --help, -h, designrail help <command> |
Print one command's usage and options |
designrail --version, -v |
Print the version |
designrail check --help and the other per-command help work without a DESIGN.md.
URLs without a scheme
scan, check and the MCP check_url tool add a scheme to bare URLs. Local development hosts get http://: localhost, 127.0.0.1, [::1], and any host ending in .localhost or .test. Everything else gets https://. So designrail check localhost:3000 checks http://localhost:3000, and designrail scan example.com scans https://example.com. A URL that already has a scheme is used as written.
scan
Loads a page in headless Chrome at 1440×900, collects computed styles and infers a design system: colours, type styles, spacing grid, radii and components.
pnpm dlx designrail scan https://www.example.com --site --install
| Flag | Default | What it does |
|---|---|---|
--site |
off | Crawl same-origin pages from the start page (two at a time), merge them, and add a 390×844 mobile pass of the start page |
--pages <n> |
10 |
Page budget for --site, including the start page |
--dark |
off | Scan with prefers-color-scheme: dark |
--out <dir> |
. |
Directory to write into |
--install |
off | Write the full project layout (below) instead of DESIGN.md and SKILL.md |
--json |
off | Also write the raw page sample as designrail.sample.json (a switch, not a file name) |
A URL without a scheme gets one added as described in URLs without a scheme. --pages must be a whole number of 1 or more. Pages are loaded with a 45-second timeout and wait for the network to go idle.
Without --install, scan writes DESIGN.md and SKILL.md to --out. With --install it writes:
| File | For |
|---|---|
DESIGN.md |
Source of truth (Google DESIGN.md format) |
.claude/skills/<name>-design-system/SKILL.md |
Claude Code (Codex reads the AGENTS.md section) |
.cursor/rules/<name>-design-system.mdc |
Cursor |
.github/instructions/<name>-design-system.instructions.md |
GitHub Copilot |
AGENTS.md |
Any agent that reads AGENTS.md |
design-system/tokens.css, tailwind-theme.css, shadcn-theme.css, tokens.json, palette.svg |
CSS variables, Tailwind v4 theme, shadcn/ui theme, DTCG tokens, palette sheet |
<name> is a slug of the design system's name. Every file is overwritten except AGENTS.md: Designrail only replaces the section between <!-- designrail:start --> and <!-- designrail:end -->, and appends that section if it is missing, so the rest of the file is kept.
On success scan prints one summary line to stdout (colours, type styles, grid, components, pages, and the Google DESIGN.md linter's error and warning counts) followed by wrote <path> for each file. With --site, progress (scanned <url>) and pages that failed to load go to stderr. For setup with a specific agent, see the guides for Claude Code and Cursor.
check
Renders a running page and compares every visible element with DESIGN.md.
pnpm dlx designrail check http://localhost:3000 --fail-under 90 --fix fixes.md
| Flag | Default | What it does |
|---|---|---|
--design <file> |
DESIGN.md |
DESIGN.md to check against |
--site |
off | Check same-origin pages too and score them together (no mobile pass) |
--pages <n> |
10 |
Page budget for --site |
--dark |
off | Render with prefers-color-scheme: dark |
--fail-under <n> |
none | Exit with code 1 when the score is below n (0–100) |
--fix <file> |
none | Write the agent fix list (Markdown) to this file |
--json <file> |
none | Write the full report as JSON to this file |
localhost:3000 and other bare local hosts are checked over http:// (see URLs without a scheme). A --fail-under or --pages value that is not a number is a usage error (exit code 2), reported before the browser starts. --fix and --json paths are relative to the current directory; --out does not apply to check.
What check scores
The score is the share of checked values that match a token: round((1 − failed ÷ checked) × 100), or 100 when nothing was checked. Elements with zero width or height are skipped. Values counted:
| Category | Checked | Passes when |
|---|---|---|
color |
Text colour (elements with text), background colour, border colour (when the border has width) | Within ΔE 1 (CIEDE2000) of a colour token or a colour written into a component spec |
contrast |
Text against what is behind it, except disabled elements | Meets WCAG AA: 4.5:1, or 3:1 for large text (24px and up, or 18.66px bold) |
spacing |
Padding, vertical margins, gap, and horizontal margins unless they are equal and 16px or more (centred) | Values from 2px to 256px are a multiple of the grid unit or within 0.5px of a spacing token |
font |
Primary font family of text elements, ignoring icon fonts and generic families | Matches a fontFamily in the typography tokens |
font-size |
Font size of text elements | Within 0.5px of a typography token's size |
radius |
Non-zero border radius | Within 1px of a rounded token; pill shapes pass when a full or large enough radius token exists |
The grid unit comes from the **Npx grid** sentence in DESIGN.md; without it, it is the greatest common divisor of the spacing tokens (4px if that is below 2). Off-token colours within a penalised distance of 25 get a nearest-token suggestion; matching a coloured value to a grey token, or the reverse, adds a penalty of 15.
The terminal shows the first 25 issues, ordered contrast, colour, font, font size, spacing, radius, then by count. The --fix file groups issues by category, up to 12 per category, with example selectors and the token to use, ready to paste into an agent.
lint-code
Scans source files line by line for values that bypass the design system. It has no parser, so it runs on any stack.
pnpm dlx designrail lint-code src app --max-warnings 0
| Flag | Default | What it does |
|---|---|---|
--design <file> |
DESIGN.md |
DESIGN.md to lint against |
--format <f> |
text |
text, github (workflow annotations) or json; any other value is a usage error |
--max-warnings <n> |
none | Exit with code 1 when there are more than n warnings; token-literal notices don't count. Must be a whole number of 0 or more |
Paths default to src. Directories are walked recursively, skipping node_modules, .git, dist, build, out, .next, .nuxt, .output, .svelte-kit, coverage, design-system, .turbo and .vercel. Files are linted when their extension is one of .css .scss .sass .less .pcss .tsx .jsx .ts .js .mjs .vue .svelte .astro .html, except *.min.* and *.d.* files. A path that does not exist is skipped, not an error.
Rules
| Rule | Finds | Where |
|---|---|---|
raw-color |
Hex, rgb(), hsl(), oklch(), oklab(), lab(), lch() literals not in the palette; suggests the nearest token when one is close. Colours under 50% opacity (overlays, scrims, tints) are skipped |
All files |
token-literal |
A token's exact value typed out instead of referenced (lower severity) | All files |
palette-class |
Default Tailwind palette classes such as bg-blue-500, text-slate-700/50, hover:border-red-200 |
All files |
off-grid |
Arbitrary Tailwind spacing such as p-[13px], gap-x-[7px], top-[5px] off the grid |
All files |
off-grid |
margin, padding, gap px values off the grid (values using var() are skipped) |
CSS |
unknown-font |
font-family whose first family is not a typography token (generic families and var() skipped) |
CSS |
"CSS" means every line of .css, .scss, .sass, .less and .pcss files, and the lines inside <style> blocks of .vue, .svelte, .astro and .html files. Inline style="…" attributes only get the all-files rules. Colours in custom property definitions (--brand: #533afd) and in shadow declarations are not flagged. Values of 0 and ±1px are always on the grid.
Translucent colours from 50% to 99% opacity are compared by their own colour, not by how they would look over white: rgb(79 70 229 / 0.8) is the primary token at 80% and is not flagged, while rgb(220 0 0 / 0.9) is flagged with the nearest token as the suggestion.
A line is skipped when it contains designrail-ignore anywhere, usually in a comment such as {/* designrail-ignore */} at the end of a JSX line or /* designrail-ignore */ in CSS. Lines that start with //, * or /* are skipped too.
Output
Text output, one finding per line, then a total with the number of warnings:
src/entrypoints/sidepanel/style.css:195:61 off-grid 3px is off the 2px grid. → {spacing.3xs} 2px
src/entrypoints/sidepanel/style.css:209:38 token-literal #fff is the background token typed out; reference the token instead. → {colors.background}
20 findings (14 warnings) in 17 files checked against Stripe.
--format github prints one annotation per finding, ::notice for token-literal and ::warning for the rest, with no total line:
::warning file=src/entrypoints/sidepanel/CheckView.tsx,line=242,col=43,title=design system (raw-color)::#e11d48 is not in the Stripe palette.
--format json prints an array of findings with file, line, column, rule, value, message and, when there is one, suggestion. --max-warnings counts warnings only, matching the github format: a token-literal finding is a notice and never fails the run. When the limit is exceeded, a line such as "3 warnings, more than --max-warnings 0." goes to stderr, so JSON on stdout stays parseable.
mcp
pnpm dlx designrail mcp --design ./DESIGN.md
| Flag | Default | What it does |
|---|---|---|
--design <file> |
DESIGN.md |
DESIGN.md to serve, resolved against the directory the server starts in |
Starts an MCP server on stdio. Tools, resource and client setup are on the MCP server page.
Browser and environment
scan, check and check_url drive a headless browser with puppeteer-core; no browser is downloaded. The first match wins:
| Platform | Paths tried in order |
|---|---|
| Windows | Chrome in Program Files, then Program Files (x86); Edge in Program Files (x86), then Program Files |
| macOS | Google Chrome, Microsoft Edge, then Chromium in /Applications |
| Linux and others | /usr/bin/google-chrome, google-chrome-stable, chromium, chromium-browser, microsoft-edge |
| Variable | Effect |
|---|---|
CHROME_PATH |
Browser executable to use instead of the search above |
CI |
When set, Chrome is launched with --no-sandbox --disable-dev-shm-usage |
DEBUG |
When set, errors print a stack trace instead of the message |
If no browser is found the command fails with "No Chrome or Edge found. Install one, or set CHROME_PATH to the browser executable."
Exit codes
| Code | When |
|---|---|
0 |
Success, including check without --fail-under and lint-code without --max-warnings |
1 |
check score below --fail-under; lint-code warnings above --max-warnings; a runtime error such as no browser or a page timeout |
2 |
Any usage error: unknown command or flag, a flag without its value, scan or check without a URL, a --format other than text, github or json, a non-numeric --fail-under, --max-warnings or --pages, or a --design file that cannot be read |
Errors are printed to stderr as designrail: <message>; usage errors add a line pointing to the command's --help. To run these checks on every pull request, use the GitHub Action.
FAQ
Do I need Chrome installed to lint code?
No. lint-code only reads DESIGN.md and your source files. Chrome or Edge is needed for scan, check and the MCP check_url tool.
Why does check fail against my local dev server?
Make sure the server is running and the port is right. Bare localhost, 127.0.0.1, [::1], *.localhost and *.test URLs are loaded over http://; if your dev server uses HTTPS, or a different local hostname, write the scheme yourself, for example https://myapp.local:3000.
Will scan --install overwrite my AGENTS.md?
No. It only replaces the block between <!-- designrail:start --> and <!-- designrail:end -->, or appends one. All other files in the layout are overwritten.
How do I silence one finding?
Put designrail-ignore anywhere on that line, usually in a comment. It applies to that line only.