brandmd
[npm version] [DESIGN.md lint: clean] [License: MIT]
Stop Claude Code, Cursor, and Gemini CLI from guessing your UI. brandmd extracts any website's design system into a spec-valid DESIGN.md that passes the official @google/design.md linter.
AI coding agents generate generic screens when they don't know your colors, fonts, spacing, components, and layout rules. Run one command, drop DESIGN.md in your project root, and your agent has brand context before it writes code. Output is the official DESIGN.md format: YAML frontmatter with machine-readable tokens plus canonical prose sections. Default extraction runs locally with no API key.
Every generated DESIGN.md validates clean:
Pass multiple URLs to merge brand tokens across pages: npx brandmd https://site.com https://site.com/pricing https://site.com/docs -o DESIGN.md
Outputs to whatever your stack reads:
DESIGN.md (default): for Claude Code, Cursor, Gemini CLI, Codex, Stitch
--json: raw extracted tokens, in brandmd's internal shape (it can change in a 1.x release, see Stability)
--css: CSS custom properties, drop into any project
--tailwind: Tailwind v4 @theme block
--html: visual, shareable brand guide
Combine with:
--agent: also writes .cursor/rules/brand.mdc and SKILL.md to both .agents/skills/brand-style/ (the universal Agent Skills path used by skills.sh across 50+ agents) and .claude/skills/brand-style/ (backward-compat for direct Claude Code users). No manual wiring. Picked up automatically by Claude Code, Cursor, Codex, Gemini CLI, Kiro CLI, and the rest of the skills.sh ecosystem. Schema-compatible with Anthropic Claude Code Skills, google/skills, and vercel-labs/agent-skills.
--dark: also extract dark mode tokens
--vision: adds illustration style, photography mood, copywriting voice, and microcopy patterns from a screenshot. Requires GEMINI_API_KEY or GOOGLE_API_KEY (free at aistudio.google.com/apikey). Uses gemini-3.8-flash; set BRANDMD_VISION_MODEL to pick another model. If the screenshot or the vision call fails the CSS-only DESIGN.md is still written and the run exits 1.
Or let your agent run it. brandmd mcp is an MCP server with one tool, extract_design_md, so Claude Code, Cursor, or any MCP client can pull a site's DESIGN.md in the middle of a task ("make this page look like linear.app"):
For Cursor, Claude Desktop, and other clients that read an mcpServers config:
Or in one click, which adds that same config:
[Add to Cursor] [Install in VS Code]
It is also in the MCP Registry as io.github.yuvrajangadsingh/brandmd.
The tool takes one or more pages of a site (urls) and an optional dark flag, and refuses block pages and login walls the same way the CLI does. It renders pages in Playwright's headless Chromium; if that browser is missing, the call fails with the command that installs it.
A call runs for at most 180 seconds (set BRANDMD_MCP_TIMEOUT in seconds to change it), and at most two run at once (a third gets an error asking it to retry). Cancelling a call, or running out of time, stops it and closes its browser on macOS and Linux. On Windows the run is ended outright and the browser may be left open (untested).
Works as brand context for Claude Code, Cursor rules, Gemini CLI, Codex, Google Stitch, MCP servers, and any coding agent that reads markdown.
Real examples: Stripe · Linear · GitHub · Vercel · Notion · Cursor · Figma · Supabase · Raycast · +20 more in the gallery
[brandmd extracting Stripe into DESIGN.md for Claude Code, Cursor, and Google Stitch]
The YAML frontmatter is the machine-readable token layer; the prose sections carry the evidence-based rationale (tiered palette, confidence tags, observed component styles). brandmd gives Claude Code, Cursor, Gemini CLI, and Codex real design context: it extracts a live website's colors, typography, spacing, shadows, component patterns, and layout rules, so AI coding agents build on-brand UI instead of generic screens.
Installable example skills repo
If you want ready-made brand skills without running brandmd yourself, install yuvrajangadsingh/brand-skills directly:
That installs 5 brandmd-generated skills (Tailwind CSS, shadcn/ui, Vercel, Mintlify, Anthropic) into your project's agent skills folder. Each skill is self-contained with a bundled references/DESIGN.md. Community examples generated from public marketing sites, not affiliated with the named brands.
Release notes
- v1.0: Stable release. A stated 1.x contract (see Stability), bounded MCP calls, buttons read from the page's real call to action, a palette that no longer depends on colour order, and one refusal rule for every page and theme.
- v0.14: Spec + truth release. Emits the official DESIGN.md format (YAML frontmatter + canonical sections) that passes
@google/design.md lint clean. Fail-closed refusals with a distinct exit code (--allow-blocked to override), atomic writes, gradient-stop parsing, honest CTA/button selection, shadow values in DESIGN.md, and a fixed generate/parse/diff round trip.
- v0.13: Detects block / access-denied pages (Akamai, PerimeterX, WAF 403s) and warns instead of emitting a garbage design system. Notes when a site uses motion (canvas, WebGL, Lottie, rAF).
- v0.12: Trust-repair: evidence-based visual character, tiered palette, HSL color naming, no more scientific-notation radii, clustered type scale.
- v0.11:
diff subcommand and the hosted example gallery.
- v0.10: Universal
.agents/ skills path.
- v0.9:
--agent flag writes Cursor rule + Claude Code skill alongside DESIGN.md. CI switched to npm Trusted Publishing.
- v0.8: More accurate primary font detection across display, heading, body, and global text roles.
- v0.7: Cloudflare-protected sites handled by waiting up to 20s for the JS challenge.
- v0.6: Optional
--vision adds illustration style, photography mood, copywriting voice, and microcopy notes. Requires a free Gemini API key.
See CHANGELOG.md for the full history.
Examples
See all 29 DESIGN.md examples in the gallery →
Flagship deep links (DESIGN.md):
Other output formats:
Why
DESIGN.md is an open spec (Apache 2.0) for encoding a design system in a format LLMs can read: YAML frontmatter with typed tokens plus canonical prose sections. There's an official @google/design.md CLI that lints, diffs, and exports it. Problem is, nobody wants to write one from scratch.
brandmd does it from the terminal. Point it at any URL, get a spec-valid DESIGN.md back that passes @google/design.md lint clean. Drop it in your project root and your AI tools start generating on-brand UI.
Install
Needs Node 20 or newer (Playwright and the Gemini SDK both require it).
Usage
Refusals and exit codes
brandmd fails closed. If a page is a bot-block / access-denied page (a Cloudflare challenge that never clears included), a login / sign-in wall landing, or too thin to describe (no rendered text, empty palette), it refuses in every format and writes no artifact, so a bad capture can't silently overwrite a good DESIGN.md. A login wall means a password field on screen when the page loads, or sign-in copy on an auth page: a /login-style URL, or another host the request was sent to. A hidden or off-screen sign-up form, or a "Sign up free" headline on an ordinary page, is not one. A plain cross-origin redirect (say marketing -> app on another domain) doesn't refuse: it warns on stderr and is flagged in the output's provenance block, per page.
Every capture goes through the same gate, page by page. One refused page (a block page, a login wall, or a page with almost no text, even next to a rich one) refuses a multi-page run, and with --dark the dark capture is checked too. A page that fails to load (a timeout, a DNS error) is left out with a warning and the rest are merged; if none load, the run exits 1. --dark that gets no dark capture, like --vision that gets no vision result, still writes the light output and exits 1.
Pass --allow-blocked to force output anyway; whatever the refusal was, the artifact carries a refused-source marker in every format (Markdown callout, CSS/Tailwind comment header, HTML banner, JSON blockLikely). Writes are transactional (temp file + rename with rollback), including the --agent set and brandmd diff, so a partial failure never truncates an existing file. Atomicity is per process: concurrent brandmd runs against the same output directory are not coordinated, though collision-safe temp/backup names mean one run can never eat another's files.
Gallery
See 4 real DESIGN.md snapshots in the browser (Stripe, Vercel, Linear, Mintlify), or scan the examples/ folder for all 29. Each snapshot is generated from a single public page visit and is observed, not canonical.
Check (drift detection in CI)
brandmd diff compares two brands. brandmd check asks a different question: has what we shipped drifted from the spec we agreed on?
Colours are compared by role, not as a set of hexes. One hex legitimately fills several roles (#FFFFFF is often background and on-primary), so a set comparison silently misses the drift that matters: the hex behind primary changing while that hex still appears elsewhere.
What fails the build. Losing or repainting a role, and a changed primary or secondary font, are major. New roles, extra spacing steps and reworded theme descriptions are minor and pass by default, because they are usually a new component landing rather than a regression. Theme wording is brandmd's own interpretation, so it never fails a build on its own.
Pin the viewport in CI. It decides which breakpoint renders, so an unpinned run can legitimately extract a different design system on a different machine and report drift that isn't there.
A blocked page, an evidence-thin page, or a login-wall landing refuses with exit 2 rather than reporting drift. Diffing a Cloudflare challenge against a real spec would flag every token as changed, and a check that cries wolf is one people turn off. An empty or truncated DESIGN.md exits 1, because a gate that silently stops gating is worse than no gate.
What check does not see
Worth knowing before you rely on it:
- Component drift is reported but does not fail a build by default. Background, text colour and radius are compared on the machine-token components; height and padding are not. Components are the least certain part of an extraction (a button that fades in, or one that paints no fill at rest, is missed; see Known limits), so component drift is always
minor. Opt in with --fail-on any.
secondaryFont is unreliable. The live side is round-tripped through the same generator and parser as a written DESIGN.md, which keeps both sides honestly comparable but drops fields the markdown does not carry cleanly. A secondary-font change can pass unnoticed.
- Majors can flip on sites that change between visits. Each colour cluster is led by its most-used colour, so the order a page lists its colours in no longer changes the palette or the text roles, but rotating hero imagery or an A/B test that changes how often a colour is used can still repaint a role. On static marketing pages runs are byte-identical; on dynamic ones, re-run before believing a surprising major.
In GitHub Actions
Exit 3 fails the job on drift; 1 and 2 still mean the tool errored or refused, so a broken preview never reads as a design change. When drift is intentional, update DESIGN.md in the same PR and the check goes green.
Diff
Compares two DESIGN.md files and writes a markdown diff: shared and unique colors, typography table, spacing and radii table, per-component property diff, and a "what to copy / what to avoid" synthesis. Local files only. See examples/diff-stripe-vs-vercel.md for the output format.
Output formats
DESIGN.md (default)
The official DESIGN.md format: YAML frontmatter (machine-readable colors, typography, rounded, spacing, components tokens) plus canonical sections (Overview, Colors, Typography, Layout, Elevation & Depth, Shapes, Components, Do's and Don'ts). Validates clean under @google/design.md lint. Drop it in your project root and AI coding agents use it to generate on-brand UI.
CSS custom properties (--css)
Tailwind v4 (--tailwind)
HTML brand guide (--html)
A self-contained dark-themed HTML page with color swatches, font specimens, spacing visualization, and shadow examples. Open it in a browser or share it with stakeholders.
JSON (--json)
Raw extracted tokens for programmatic use. The shape is brandmd's internal one and can change in any 1.x release; see Stability.
Multi-page extraction
Pass multiple URLs to merge tokens from different pages into one DESIGN.md. Each page contributes equally regardless of length: token frequencies are normalized per page, and components carry a per-page weight, so a docs page with fifty buttons can't outvote a homepage with three. What that decides is on how many pages a pattern appears, not how many times it appears anywhere, which is the right question to ask of a brand pattern.
Failed pages are skipped with a warning. Mixed domains show a warning.
Dark mode
Extract dark theme tokens as a separate section:
Adds a "Dark Mode Overrides" section to the DESIGN.md with the dark color palette. Uses prefers-color-scheme: dark via Playwright, so it captures what users actually see in dark mode. Affects DESIGN.md and --json output (ignored for --css, --tailwind and --html).
What it extracts
- CSS custom properties from
:root and @media rules (uses actual variable names when available)
- Colors with semantic roles (background, text, accent, border)
- Typography with role-aware Primary detection (display > heading > body), per-role font breakdown, and a full "all detected fonts" frequency list. Skips monospace, default fallbacks, and icon fonts when picking Primary.
- Spacing scale and base grid unit
- Border radii and shadow styles
- Component patterns (buttons, cards, inputs). A button is a
<button>, a role="button", or a link classed or painted like one (a fill, a gradient or a border other than an underline, with a short visible label); hidden and off-page controls, skip links and controls inside a <table> (a sample email, a data grid) are left out, unless every control on the page sits in a table, and a control with no label text (an icon-only launcher, or only an icon font's glyphs) is never the primary button. The primary button is the most repeated fill with real colour (LCH chroma), unless that colour appears once and a neutral fill repeats more; then, as when every fill is neutral, it is the neutral that stands out most against the page. colors.primary is that button's fill as the page shows it (a translucent fill composited over the page, a gradient button's first stop); with no solid button, or one in the page's own colour, it falls back to the most vivid accent colour. The prose names the element each button was sampled from.
Known limits
- A hero button that fades in as the page loads or scrolls can be missed. A headless capture may never run the page's reveal, so the button stays at opacity 0 and is left out, and the primary comes from the other buttons.
- A page whose buttons paint no fill at rest (transparent until hovered) has no primary button to sample. Unless another accent colour stands out on the page,
colors.primary falls back to the page colour and the output says it is low confidence. clerk.com is one in the gallery.
How it works
- Renders each page in a headless browser (Playwright)
- Scrolls through to trigger lazy-loaded content
- Dismisses cookie banners and overlays
- Extracts CSS custom properties from
:root (recurses into @media rules)
- Extracts computed styles from every visible element
- Clusters similar colors, identifies the spacing scale
- Merges tokens across pages (normalized per page)
- Outputs in your chosen format
No LLM calls, no API keys, runs entirely on your machine.
Agent skill
brandmd ships as an Agent Skill that works across 30+ platforms including Claude Code, Cursor, VS Code/Copilot, Codex, and Gemini CLI.
Then tell your agent: "extract the design system from https://linear.app"
If brandmd saved you time, consider starring the repo.
Built by Yuvraj Angad Singh. Also: vibecheck (catch AI-generated code smells) and vemb (embeddings from the command line). Follow on GitHub for new dev-tool experiments.
Stability
From 1.0.0 brandmd follows semver for the parts below. A 1.x release can add to them (a flag, a section, a tool input); removing or changing one waits for 2.0.
Covered:
- Commands and flags.
brandmd <urls...>, brandmd diff, brandmd check and brandmd mcp, with every flag their --help lists: -o, --json, --css, --tailwind, --html, --dark, --vision, --agent, --allow-blocked, --debug, --cf-wait-ms, --viewport, --locale and --reduced-motion; diff --out; check --against, --report and --fail-on.
- Exit codes.
0, 1, 2 and 3, as in the table above.
- The DESIGN.md shape. The frontmatter keys
version, name, description, colors, typography, rounded, spacing and components, and the sections Overview, Colors, Typography, Layout, Elevation & Depth, Shapes, Components and Do's and Don'ts, plus Dark Mode Overrides with --dark and Visual Identity Beyond CSS with --vision. The linter it is checked against is @google/[email protected]: CI fails on any error or warning it reports for generated test files, and every example in the gallery lints clean. A later linter version counts as supported once a brandmd release says so.
- The MCP tool.
extract_design_md, its urls and dark inputs, and its result: the DESIGN.md as the first text block, warnings as a second one, and refusals and failures as tool errors carrying the CLI's message.
- Environment variables.
GEMINI_API_KEY or GOOGLE_API_KEY and BRANDMD_VISION_MODEL for --vision, and BRANDMD_MCP_TIMEOUT for the MCP server.
- Node 20 or newer.
Not covered, so any 1.x release can change them:
- The values. brandmd measures a live page, so a site change or a better extraction changes colours, fonts and components. Pin the brandmd version and commit the
DESIGN.md when you need the same file twice.
- The JSON from
--json and check --report. Both are internal JSON formats (--json prints the extracted tokens, --report the drift list), so field names and nesting can change. The MCP tool returns DESIGN.md, not this JSON. When a script needs something stable, read the DESIGN.md, or check's exit code.
- What
--css, --tailwind and --html print. The flags stay; the variable names and layout follow the page.
- Wording. Prose sentences, warnings and error messages.
- The MCP limits. Today a call runs for at most 180 seconds (
BRANDMD_MCP_TIMEOUT), two run at once, and a cancelled or timed-out call stops its run and closes its browser on macOS and Linux. Every extraction's scroll pass stops after 50 screens and starts no new screen after 15 seconds. These numbers can be tuned.
License
MIT