
Html To Design Md
- 1 installs
- 1 repo stars
- Updated June 17, 2026
- aaione/html-to-design-md
html-to-design-md is a Claude Code skill that extracts design tokens from an observed web UI and writes a lint-clean DESIGN.md design-system spec.
About
html-to-design-md is a Claude Code skill that turns an observed web UI into a lint-clean DESIGN.md design-system spec. It extracts colors, typography, spacing, radii, and components from a website URL, localhost page, or local HTML file, using source-faithful evidence rather than invented aesthetics. A developer uses it to capture an existing UI's design system. The output follows the @google/design.md format and is validated with the design.md linter.
- Extracts design tokens from a URL, localhost, or HTML file
- Writes a @google/design.md-format DESIGN.md
- Validates output with npx @google/design.md lint
Html To Design Md by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,609 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Jul 7, 2026 (Skillselion catalog sync)
html-to-design-md capabilities & compatibility
- Capabilities
- design token extraction · design system spec
- Works with
- playwright
- Use cases
- ui design · web design · frontend · documentation
- Pricing
- Free
What html-to-design-md says it does
Turn a live website, localhost page, or local `.html` file into a `DESIGN.md` that follows the `@google/design.md` format
npx -y @google/design.md lint <output-dir>/DESIGN.md
npx skills add https://github.com/aaione/html-to-design-md --skill html-to-design-mdAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 1 |
| Last updated | June 17, 2026 |
| Repository | aaione/html-to-design-md ↗ |
What it does
Extract real design tokens from a website, localhost page, or HTML file and write a lint-clean DESIGN.md design-system spec.
When should I use this skill?
When you want a DESIGN.md with design tokens extracted from a website, localhost page, or HTML file.
What you get
- DESIGN.md
- Design tokens (colors, typography, spacing, radii, components)
By the numbers
- Emits at minimum name, colors, typography, rounded, spacing, and components
Files
HTML To DESIGN.md
Overview
Turn a live website, localhost page, or local .html file into a DESIGN.md that follows the @google/design.md format: YAML frontmatter with machine-readable tokens plus Markdown rationale. Preserve observed source truth; do not invent aesthetics.
Keep references/design-md-format.md open for the token schema, canonical section order, and the authoritative lint-rule table. Mirror references/EXAMPLE.md for a complete, lint-clean structure. Follow references/worked-example.md for a full HTML→DESIGN.md extraction walk (input → token decisions → lint).
Workflow
1. Resolve inputs. The source URL/localhost/local HTML and the optional output dir come from $ARGUMENTS (<url-or-html-path> [output-dir]). If $ARGUMENTS is empty, ask the user for a URL/path. Always write to <output-dir>/DESIGN.md (default: current working directory).
2. Collect evidence. Use the strongest inspection path available for the input:
| Input | Required evidence path | Stop condition |
|---|---|---|
Local .html | Read the file, inline styles, and linked local CSS/assets that are accessible from the file path. | Enough CSS/markup exists to identify real tokens and components. |
| Website URL | Use a browser/rendering or fetch tool when available; inspect HTML, CSS, computed styles, and screenshots if rendered. | The target page, not an interstitial/login, is observable. |
| Localhost | Render with the active browser/tool if the server is already running; otherwise use reachable HTML/CSS only. | The page resolves with meaningful DOM and styles. |
| SPA shell | Require rendered DOM/computed styles. | Halt if only an empty mount node is available and no browser/rendering tool exists. |
| Multi-page input | Preserve input order, extract shared tokens first, then record page-specific variants. | Shared system plus named deviations are documented. |
3. Inspect the design. Capture token evidence — colors, typography, spacing scale, radii, shadows, component states, layout density, responsive behavior, viewport behavior, and domain-specific components. Inspect source and computed styles before relying on screenshot inference; mark any inferred value in prose, never in a token name. Prefer intent tokens (theme / CSS-variable files) over computed styles over screenshots — see Extraction Source Priority and Reverse-engineering from Frameworks in the reference.
4. Extract tokens. Pull exact values from CSS variables, computed styles, framework classes, or inline styles. Emit at minimum name, colors (including a primary), typography, rounded, spacing, and the observable components. Group colors by role (primary/accent/surface/on-*/functional states like success/error), deduplicate near-duplicates, and reference every color from a component to avoid orphaned-token warnings. Always emit 1-2 domain-specific components beyond generic primitives. For a dark/alternate theme, add separate scalar tokens (e.g. surface-dark: "#1A1C1E") and document the mapping in the Colors section — color arrays ([light, dark]) are rejected by the current linter.
5. Write `DESIGN.md`. Frontmatter delimited by ---; exact sRGB hex colors; dimensions with units; component tokens reference tokens like {colors.primary}. Follow the canonical section order (see the reference). Prose should state why tokens exist and how future UI applies them. Mirror references/EXAMPLE.md.
6. Validate (blocking). From the output directory run, and treat any error as blocking — fix and rerun until exit 0:
npx -y @google/design.md lint <output-dir>/DESIGN.mdHard errors (exit 1) are broken-ref and schema failures such as an invalid color value (e.g. a color array → is not a valid color); the rest are warnings/info. Warnings (e.g. contrast-ratio, orphaned-tokens) are allowed only when source-faithful — mention remaining ones in the final response. The validator is unpinned (@google/design.md), so a pass is authoritative only for the currently-resolved version. If the environment cannot run the CLI (no network, registry blocked, etc.), report the exact failure and do a manual schema pass against the reference instead of claiming success.
Capabilities & Fallbacks
- Browser tool. Rendered-page inspection needs a browser/Playwright-style tool. If none is available, fall back deterministically: (1) fetch raw HTML, (2) read inline + linked CSS, (3) mark all computed-style-dependent tokens as inferred. Never fabricate values a browser would have shown.
- JS-rendered / SPA pages. If fetched HTML is an empty shell (mount node only), require a real browser to render; if unavailable, halt with a clear report rather than emit an empty DESIGN.md. Re-render the same source the same way so re-runs stay reproducible.
- Auth-gated pages. If the page resolves to a login/consent/interstitial, ask the user to authenticate in the active session first. Never treat login chrome as the site's design system.
- Multiple pages. Synthesize shared tokens first; the first page wins for conflicting base tokens; record deviations as named variants under Components. Preserve input order across re-runs.
Extraction Checklist
- Identity: product/site name, domain, page role, and any declared brand/theme metadata.
- Colors: semantic roles,
on-*text pairs, functional states, hover/focus fills, near-duplicate consolidation, and contrast-sensitive pairs. - Typography: font families, display/body/label scale, weights, line heights, letter spacing, and where each style appears.
- Spacing & layout: base unit, section rhythm, container widths, gutters, breakpoints, alignment patterns, and touch target sizing when visible.
- Shapes & depth: radius scale, borders, shadows, backdrop/blur behavior, elevation rules, and flat-vs-layered strategy.
- Components: buttons, cards/containers, navigation, inputs/forms when present, feedback/status elements, and 1-2 domain-specific components.
- States: default, hover, active, disabled, focus, selected, loading, and error states when observable; write unsupported states in prose, not unsupported component keys.
Quality Gate
- The frontmatter has
name,colors.primary,typography,spacing,rounded, andcomponents. - Component tokens use only recognized keys:
backgroundColor,textColor,typography,rounded,padding,size,height,width. - Every token reference resolves; every important color is referenced by at least one component unless a source-faithful exception is documented.
- Prose explains intent and application, not just raw CSS values.
- The output names concrete source limitations: inaccessible CSS, no browser, auth wall, SPA shell, or linter/runtime failure.
Output Contract
- Preserve observed source truth over aesthetic invention.
- Include enough tokens and prose for another agent to generate visually consistent screens, and validate with
npx -y @google/design.md lint --format json <output-dir>/DESIGN.mdassertingsummary.errors === 0and thatcolors,typography,spacing, andcomponentsare present (a bare exit code is not sufficient).
# macOS
.DS_Store
# Node / npx artifacts
node_modules/
npm-debug.log*
# Skill run output (generated DESIGN.md, screenshots, etc.)
output/
Repository Guidelines
Project Structure & Module Organization
This repository is an Agent Skills package for generating lint-clean DESIGN.md files from observed HTML or websites.
SKILL.mdis the canonical skill implementation and operating contract. The skills CLI symlinks this single file into every target agent's skills dir — edit once, never fork per-agent adapters.README.mdexplains install, usage, validation, and compatibility.references/design-md-format.mddocuments the target@google/design.mdschema and lint rules.references/EXAMPLE.mdis the primary lint-clean fixture and structure example.agents/openai.yamlcontains Codex/OpenAI catalog metadata.
There is no compiled source tree or bundled asset pipeline; changes are primarily Markdown and YAML.
Build, Test, and Development Commands
npx -y @google/design.md lint references/EXAMPLE.mdvalidates the bundled example fixture.npx -y @google/design.md lint <output-dir>/DESIGN.mdvalidates generated design-system output.git status --shortchecks that only intentional files changed.
No local npm install or build step is required for normal edits.
Coding Style & Naming Conventions
Use concise Markdown with sentence-case prose and clear imperative instructions. Keep examples copy-pasteable and prefer fenced code blocks for commands. YAML files use two-space indentation and quoted strings where existing metadata does. Preserve the skill's source-faithful language: do not add speculative design behavior or unsupported extraction capabilities.
Testing Guidelines
The main regression check is the @google/design.md linter against references/EXAMPLE.md and any newly generated DESIGN.md. Treat lint errors as blocking. Warnings are acceptable only when they reflect source-faithful output and should be mentioned in review notes. If you change schema guidance, update references/EXAMPLE.md when needed so the documented pattern remains lint-clean.
Commit & Pull Request Guidelines
Commits follow the Conventional Commits specification. Each commit message uses this format:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]- type (required, lowercase) — one of:
feat— a new feature or skill capabilityfix— a bug fix or lint/correctness fixdocs— documentation-only changes (README.md,SKILL.mdguidance,references/*)style— formatting, whitespace, or wording with no behavior changerefactor— code/structure change that neither fixes a bug nor adds a featureperf— a change that improves performancetest— adding or correcting tests/fixturesbuild— changes to build tooling or dependenciesci— changes to CI/automation configurationchore— routine maintenance (e.g..gitignore, dependency bumps)- scope (optional) — a short noun in parentheses naming the affected area, e.g.
docs(skill),fix(example),fix(design-md). - description (required) — imperative mood, lowercase, no trailing period, focused on user-visible intent. For example:
feat: enrich extraction methodology + example coverage,fix: cross-CLI docs + design.md correctness,chore: drop redundant tests/sample.html fixture. - Breaking changes — append
!after the type/scope (feat!:) and/or add a footer line starting withBREAKING CHANGE:describing the change and migration notes. - body (optional) — wrap at ~72 columns; explain the why, not the what. Omit if the description is self-explanatory.
- footer (optional) — reference issues/tickets (
Closes #12) or add metadata likeBREAKING CHANGE:.
Examples
feat(skill): add lint-clean scalar dark variant to EXAMPLEfix(design-md): correct token naming across sections
Token names drifted from the @google/design.md schema in the
typography section. Re-aligned with references/design-md-format.md.
Closes #8feat!: rename `tone` field to `mood` in extracted palette
BREAKING CHANGE: consumers must rename `tone` → `mood`.Pull requests should describe the changed guidance, list validation commands run, and call out any remaining lint warnings or unverified paths. The PR title should itself be a valid Conventional Commit subject. Link related issues when available. Include before/after snippets only for substantial documentation or schema changes.
interface:
display_name: "HTML to DESIGN.md"
short_description: "Generate DESIGN.md from websites or HTML"
default_prompt: "Use $html-to-design-md to generate a DESIGN.md from this website or local HTML file."
# Catalog / publishing metadata for the open skills ecosystem (skills.sh).
metadata:
version: "0.2.0"
license: "MIT"
source_repo: "https://github.com/aaione/html-to-design-md"
validator: "@google/design.md"
compatibility:
- claude-code
- codex
keywords:
- design-system
- design-tokens
- design-md
- html
- extraction
- lint
CLAUDE.md
@AGENTS.md
MIT License
Copyright (c) 2026 aaione
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.html-to-design-md
Turn any website, localhost page, or local HTML file into a lint-clean `DESIGN.md` design system.

An Agent Skills skill that inspects an observed UI, extracts real design tokens (colors, typography, spacing, radii, components), and writes a DESIGN.md that passes @google/design.md lint. The output is source-faithful — it captures what the UI actually uses rather than inventing an aesthetic.
Install
npx skills add aaione/html-to-design-md # default agent
npx skills add aaione/html-to-design-md -a codex # target a specific agent
npx skills add aaione/html-to-design-md -g # global installOne canonical SKILL.md works across many agents via the skills CLI (vercel-labs/skills), which symlinks it into each agent's skills directory (.claude/skills/, .agents/skills/, .codex/skills/, …) — no per-agent adapters needed. Install flags and the supported-agent list follow whatever the installed CLI reports (skills add --help). (agents/openai.yaml carries optional Codex catalog metadata.)
Use
Invoke the skill with a source and an optional output directory:
/html-to-design-md https://example.com ./design
/html-to-design-md ./site/index.htmlIt writes DESIGN.md (frontmatter tokens + rationale prose) into the output directory, then validates it:
npx -y @google/design.md lint ./design/DESIGN.mdWhat it produces
A DESIGN.md with machine-readable frontmatter and eight rationale sections:
---
version: "alpha"
name: Atlas Editorial
colors:
primary: "#1A1C1E"
accent: "#B8422E"
background: "#F7F5F2"
typography:
h1:
fontFamily: Public Sans
fontSize: 48px
fontWeight: 600
lineHeight: 1.1
rounded:
sm: 4px
md: 8px
spacing:
sm: 8px
md: 16px
components:
button-primary:
backgroundColor: "{colors.accent}"
textColor: "#FFFFFF"
rounded: "{rounded.sm}"
---
## Overview
...See `references/EXAMPLE.md` for a complete, lint-clean sample covering all eight sections.
How it works
1. Collect evidence from the richest available source: local HTML/CSS, rendered URL/localhost DOM, computed styles, screenshots, or framework theme files. 2. Inspect the visual system across colors, typography, spacing, radii, shadows, component states, responsive behavior, and domain-specific components. 3. Extract exact token values from CSS variables, computed styles, framework classes, and inline styles. For dark/alternate themes, add separate scalar tokens (e.g. surface-dark) and note the mapping in prose — recent linter behavior rejects color arrays. 4. Write DESIGN.md following the canonical section order, mirroring EXAMPLE.md. 5. Validate with @google/design.md lint until it exits 0; broken-ref and schema failures such as invalid colors are hard errors.
Full operational rules, edge cases (SPA shells, auth-gated pages, multi-page synthesis), and the token schema live in `SKILL.md` and `references/design-md-format.md`. A complete HTML→DESIGN.md extraction walk lives in `references/worked-example.md`.
The skill is deliberately evidence-first. If the page is only an auth wall, consent screen, empty SPA shell, or otherwise cannot be inspected with available tools, it reports the blocker instead of fabricating a design system.
Self-check
The bundled example doubles as a fixture:
npx -y @google/design.md lint references/EXAMPLE.md # expect exit 0Use a Node/npm version compatible with the resolved @google/design.md package.
Compatibility
| Agent | Status | Adapter |
|---|---|---|
| Claude Code | Supported | SKILL.md |
| Codex / OpenAI | Supported | agents/openai.yaml |
| Cursor, Cline, Copilot, Gemini, others | Standard SKILL.md via skills CLI | — |
License
MIT — see LICENSE.
DESIGN.md Format Reference
Authoritative compact reference for generating or repairing DESIGN.md files validated by the @google/design.md CLI. Source of truth: the currently resolved @google/design.md linter (npx @google/design.md lint). If upstream docs and the CLI disagree, the CLI result wins and the final response should mention the version/runtime used. For a full HTML→DESIGN.md extraction walk, see worked-example.md; for a finished lint-clean system, see EXAMPLE.md.
Structure
DESIGN.md has two layers:
1. YAML frontmatter (delimited by ---) with machine-readable tokens. 2. Markdown body with human-readable design rationale.
---
version: "alpha"
name: Example System
description: Short description of the visual identity.
colors:
primary: "#1A1C1E"
secondary: "#6C7278"
background: "#F7F5F2"
typography:
body-md:
fontFamily: Inter
fontSize: 1rem
fontWeight: 400
lineHeight: 1.5
letterSpacing: 0em
rounded:
sm: 4px
md: 8px
spacing:
sm: 8px
md: 16px
components:
button-primary:
backgroundColor: "{colors.primary}"
textColor: "#FFFFFF"
typography: "{typography.body-md}"
rounded: "{rounded.md}"
padding: 12px
---
## Overview
...Token Schema
version: <string> # optional, current value "alpha"
name: <string> # REQUIRED
description: <string> # optional
colors:
<token>: <Color> # any valid CSS color; hex "#1A1C1E" is the recommended default (arrays rejected)
typography:
<token>: # camelCase keys recommended; kebab-case also accepted
fontFamily: <string>
fontSize: <Dimension> # e.g. 16px, 1rem
fontWeight: <number> # e.g. 400
lineHeight: <number> # e.g. 1.6
letterSpacing: <Dimension> # e.g. -0.02em, 0.1em
# optional: fontFeatureSettings / fontVariationSettings (variable fonts),
# fontStyle, fontStretch — accepted by the linter but non-standard
# consumption fields; use to record source-faithful type detail, not as
# primary tokens.
rounded:
<level>: <Dimension> # sm/md/lg/full(9999px)
spacing:
<level>: <Dimension | number>
components:
<name>:
<prop>: <string | token reference> # ONLY recognized: backgroundColor, textColor, typography, rounded, padding, size, height, widthToken Rules
- Colors accept valid CSS color values;
"#1A1C1E"-style hex is the recommended default.oklch(),hsl(), 8-digit hex ("#RRGGBBAA"), and named colors are accepted by the current CLI; newer functions such ascolor-mix()may still fail, so validate unusual source-native colors before preserving them. Do not use arrays (surface: [light, dark]): the current CLI rejects them withis not a valid color, which is a build-failing error (see Validation), not a warning. For dark/alternate themes, add separate scalar tokens (e.g.surface-dark) and describe the mapping in the Colors prose. - Dimensions include units (
px,rem,em). - Token references use braces, e.g.
{colors.primary},{typography.body-md},{rounded.md},{spacing.md}. Unresolvable references are the one hard lint error. - Component sub-tokens are whitelisted: only
backgroundColor,textColor,typography,rounded,padding,size,height,widthare recognized — any other key (e.g.borderColor,aspectRatio) triggers an "unrecognized sub-token" warning. Express anything else (focus rings, aspect ratios, motion, borders) in prose. - Component variants are separate entries, e.g.
button-primary-hover. - Every defined color token should be referenced by at least one component (unreferenced colors warn as orphaned).
Extraction Source Priority
Trust sources in this order; when they conflict, the higher one is the declared intent and wins, and you note the deviation in prose:
1. Intent tokens — tailwind.config / theme.ts, :root CSS custom properties, theme & font files. The developer declared these as the system. Prefer them. 2. Computed styles — the browser's resolved values on real elements. Use to confirm intent and fill gaps intent didn't name. 3. Inline / framework classes — per-element Tailwind / CSS-in-JS. Actual shipped values; use to verify and catch overrides. 4. Screenshot sampling — last resort, only when no source is reachable; always mark inferred.
Code comments and custom-property names (--brand-primary, --warm-50) are the developer telling you the design intent — read them.
Color Organization
- Group by role, not hue: primary/secondary/accent, surface/background,
on-*(text on a fill), and functional states (success/error/warning/info). - Deduplicate: consolidate near-duplicates (
#333and#2C2C2C) under one token — don't emit noise. - Name functional states semantically:
success,error, and reference each from a component (e.g.badge-success) so it isn't orphaned. - *`on-
convention:** pair every fill with its text token (primary/on-primary`) so contrast is explicit and AA-checkable.
Reverse-engineering Tokens from Frameworks
When the source uses a framework, map its primitives to DESIGN.md tokens — but always resolve against the project's own config, never framework defaults, or values won't be source-faithful.
| Stack | Where tokens live | Notes |
|---|---|---|
| Tailwind | tailwind.config.{js,ts} → theme.extend.colors/spacing/borderRadius/fontFamily | rounded-lg etc. are classes; resolve to the project's borderRadius.lg, not the 0.5rem default. |
| shadcn/ui | globals.css :root HSL vars (--primary, --radius) | Already semantic tokens — lift directly. |
| CSS-in-JS | theme object (styled-components/Emotion theme, Chakra extendTheme, MUI createTheme) | Read the theme object, not rendered styles. |
| MUI / Material | createTheme palette & typography | Map palette slots to color tokens. |
Class-to-value rules must be verified in config: rounded-sm/md/lg/xl/full, the spacing base (4px vs 8px), and the font-size scale. If no config is reachable, fall back to computed styles and mark inferred.
Per-stack recipes
- Tailwind — resolve every utility against
tailwind.config, never the framework defaults.rounded-lg→theme.extend.borderRadius.lg(framework default is0.5rem; use it only if the project hasn't overridden);text-xl→theme.fontSize.xl, carrying itslineHeight;bg-brand-500→theme.extend.colors.brand[500], named by role not scale-step. The spacing base (theme.spacing/--spacing) governs everyp-*/m-*/gap-*class. - shadcn/ui —
globals.css:rootholds HSL channel triples that are already semantic.--primary: 10 70% 45%→ emithsl(10 70% 45%)as theprimarytoken (the linter acceptshsl()directly — no hex conversion needed);--radius: 0.5rem→rounded.md. Lift--background,--foreground,--muted,--borderstraight by role. - CSS-in-JS (styled-components / Emotion / Chakra / MUI) — read the exported theme object (
theme.ts,extendTheme(...),createTheme(...)), and liftcolors/fontSizes/radii/spacedirectly into DESIGN.md tokens. Component-levelsx/cssprops override the theme — spot-check 2-3 real components for shipped deviations.
HTML / Website Extraction
Use the richest evidence path available and state gaps in the final response.
| Source | Strongest evidence | Notes |
|---|---|---|
| Saved HTML | Local markup, inline CSS, linked local CSS/assets | Resolve relative paths from the HTML file before inferring values. |
| Rendered URL / localhost | DOM, linked stylesheets, computed styles, screenshots across at least desktop and mobile when possible | Computed styles prove shipped behavior; screenshots help confirm visual hierarchy. |
| Framework source present | Theme files, Tailwind config, CSS variables, component styles | Intent tokens beat framework defaults. |
| Screenshot-only | Pixel sampling and visual inference | Last resort; keep inferred values in prose and avoid over-tokenizing. |
Reject low-evidence output. If the page is an auth wall, consent page, error page, or empty SPA shell without rendering access, report the blocker instead of generating a misleading DESIGN.md.
Minimum Output Coverage
colors.primaryplus surface/background, text/on-* pairs, accent/interactive colors, and functional states when present.- At least one body typography token and the visible heading/label scale.
- A spacing scale and radius scale that match observed layout primitives.
- Components for real UI primitives: primary button, card/container or equivalent, navigation/input/status when present, plus 1-2 domain-specific components.
- Markdown prose for non-whitelisted properties such as borders, shadows, motion, aspect ratios, blur, and responsive behavior.
- Responsive & layout stay in prose. Breakpoints, gutters, max-width, and grid columns are not frontmatter tokens — there is no
layouttop-level key, and unitlessspacingnumbers are silently ignored by the linter. Describe them in the## Layoutsection instead.
Section Order
Use ## headings in this canonical order (out-of-order sections warn):
1. Overview 2. Colors 3. Typography 4. Layout 5. Elevation & Depth 6. Shapes 7. Components 8. Do's and Don'ts
Prose-only Concerns
Several design dimensions have no frontmatter slot — the component sub-token whitelist is fixed at eight keys — so capture them in prose under the matching section:
- Motion: transition
durationscale,easingcurves, andprefers-reduced-motionbehavior →## Componentsor## Elevation & Depth. - Iconography: icon set, stroke width, optical sizing →
## Components. - Stacking / z-index: elevation layering order →
## Elevation & Depth. - Focus & affordance: focus-ring width, color, and offset →
## Components.
Never model these as component sub-tokens — borderColor, transition, zIndex, ring all warn as unrecognized.
Validation
From the directory containing the file:
npx -y @google/design.md lint DESIGN.md # non-interactive (use -y in agents/CI)
npx -y @google/design.md lint --format json DESIGN.mdExit code is 1 only when errors are found, 0 otherwise.
Lint Rules
Hard errors (exit 1) are broken-ref plus schema-validation failures — most notably an invalid color value (a color array serializes to #000,#fff and fails with is not a valid color). Everything else is a warning or info — resolve warnings when the choice is source-faithful; report any that remain.
| Rule | Severity | What it checks |
|---|---|---|
broken-ref | error | A token reference like {colors.primary} that resolves to no defined token |
invalid-color | error | A color value the CSS parser rejects — e.g. an array ["#000", "#fff"] serialized as #000,#fff. Build-failing. |
missing-primary | warning | Colors are defined but there is no primary color |
contrast-ratio | warning | A component backgroundColor/textColor pair below WCAG AA (4.5:1) |
orphaned-tokens | warning | A color token defined but never referenced by any component |
missing-typography | warning | Colors are defined but no typography tokens exist |
section-order | warning | Sections appear out of the canonical order above |
token-summary | info | Per-section count of defined tokens |
missing-sections | info | Optional sections (spacing, rounded) absent while other tokens exist |
unknown-key | warning | A top-level YAML key that looks like a typo of a known key (e.g. colours: → colors:) |
Fix all errors before completion. Common real causes of failure: unresolvable token references, a missing primary color, low-contrast component pairs, out-of-order headings, and typo'd top-level keys (caught by unknown-key).
Debug cascade: if lint reports broken-ref for a reference whose name looks correct, first scan for an unknown-key warning. A typo'd top-level key (e.g. colours: instead of colors:) silently fails to register its tokens, which then cascades into broken-ref errors on every {colors.*} reference. Fix the typo at the root; the broken-refs resolve with it.
Overview
Architectural minimalism meets journalistic gravitas. The UI reads as a premium matte broadsheet — confident ink-on-limestone neutrals with one disciplined accent that drives every interaction. Restraint is the system: a single accent per screen, generous whitespace, and two typefaces that never compete. Functional states (success/error) are explicit, dedicated tokens — never ad-hoc greens and reds.
Colors
The palette is rooted in high-contrast neutrals plus a single accent; functional states are first-class tokens.
- Primary (#1A1C1E): Deep ink for headlines, body text, and the banner.
- Secondary (#6C7278): Sophisticated slate for captions, metadata, and dividers.
- Accent (#B8422E): "Boston Clay" — the sole driver for primary actions; use once per view.
- Background (#F7F5F2): Warm limestone foundation, softer than pure white.
- Surface-dark (#1A1C1E): Dark-theme container fill (see
card-dark). Alternate themes use a separate scalar token like this — never a[light, dark]array, which the linter rejects. - Success (#1B5E20) / Error (#B71C1C): Functional-state fills for status badges; grouped by role, not hue.
- On-primary / On-accent (#FFFFFF): Text placed on the primary and accent fills; both clear WCAG AA.
Typography
Two voices: Public Sans carries the narrative across a four-step scale, Space Grotesk labels technical data.
- Display (h1): Public Sans Semi-Bold at 48px, tight tracking, for institutional hero headlines.
- Headline (headline-md): Public Sans Semi-Bold at 32px for section titles.
- Body (body-md): Public Sans Regular at 16px / 1.6 for long-form readability.
- Labels (label-caps): Space Grotesk, uppercase with 0.1em tracking, reserved for buttons, badges, and taxonomy.
Layout
An 8px spacing scale governs everything; content columns sit within a 1200px max width. Use spacing.md as the default gutter and spacing.lg to separate major regions. Cards and heroes align to the same 8px rhythm so density stays predictable across pages.
Elevation & Depth
Depth is implied by contrast and surface, not heavy shadow. Keep surfaces flat on the limestone background; reserve a single level of separation (a 1px secondary-toned hairline or a subtle inset) for interactive cards. Avoid drop shadows — they break the matte editorial feel.
Shapes
Corner radius follows a four-step scale. Buttons take rounded.sm (4px) to stay crisp; cards take rounded.lg (12px); rounded.md (8px) covers inputs; rounded.full (9999px) makes pills of status badges. Never mix radii within the same component group.
Components
- Buttons: Primary actions use the accent fill with white label-caps text (≈5.4:1). The
hoverstate darkens to #9E3A28; thefocusstate inverts to the primary fill for high visibility — states are modeled as separate component entries using recognized sub-tokens. - Cards: Content cards on the background fill with
primarytext,body-md,rounded.lg,spacing.md. Thecard-darkvariant inverts tosurface-dark— alternate themes via scalar tokens, not arrays. - Hero: Full-bleed headline block,
h1type in primary ink. - Banner: High-contrast strip on the primary fill with
on-primarytext. - Caption: Secondary slate text for metadata beneath images and cards.
- Badges: Pill-shaped (
rounded.full) status indicators —badge-successandbadge-errorshow how functional-state colors are bound to components. - Product card: A domain-specific component named for the listing it represents — same surface treatment as a card (its 4:3 image ratio is noted in prose, since
aspectRatiois not a recognized token slot). Always emit 1-2 components unique to the site beyond generic primitives.
Do's and Don'ts
- Do use the accent for the single most important action per screen.
- Do model functional states (success/error) as dedicated, named tokens — not ad-hoc hex values.
- Do keep text on fills at the
on-*tokens to hold WCAG AA. - Don't mix sharp and rounded corners, or introduce a second accent — the system is intentionally monochromatic-plus-one.
- Don't apply drop shadows; separate surfaces with hairlines and whitespace instead.
Worked Example: HTML → DESIGN.md
A complete, copy-pasteable walk from a real HTML snippet to a lint-clean DESIGN.md. Where `EXAMPLE.md` is the destination (a finished, lint-clean system), this file is the path — it shows the extraction reasoning so token granularity and prose choices stay reproducible across runs.
The output frontmatter below is independently verified lint-clean when saved as a standalone DESIGN.md (npx -y @google/design.md lint → exit 0, 0 errors, 0 warnings).
---
Input
A small e-commerce listing built on the Tailwind CDN — two product cards, a masthead, and a button with a hover state.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Atlas Goods</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-stone-100 text-stone-900 font-sans antialiased">
<header class="max-w-5xl mx-auto px-6 py-8">
<h1 class="text-4xl font-semibold tracking-tight">Atlas Goods</h1>
<p class="mt-2 text-stone-500 text-lg">Considered objects for everyday use.</p>
</header>
<main class="max-w-5xl mx-auto px-6 pb-16 grid grid-cols-2 gap-6">
<article class="bg-white rounded-xl p-6 shadow-sm">
<img src="mug.jpg" class="w-full h-40 object-cover rounded-lg mb-4" alt="Mug">
<h2 class="text-xl font-semibold">Ceramic Mug</h2>
<p class="text-stone-500 mt-1 text-lg">Hand-glazed, 300ml.</p>
<button class="mt-4 inline-flex items-center px-4 py-2 rounded-md
bg-orange-700 text-white text-sm font-medium
hover:bg-orange-800 focus:ring-2 focus:ring-orange-700">
Add to cart
</button>
</article>
<article class="bg-white rounded-xl p-6 shadow-sm">
<img src="book.jpg" class="w-full h-40 object-cover rounded-lg mb-4" alt="Book">
<h2 class="text-xl font-semibold">Field Notes</h2>
<p class="text-stone-500 mt-1 text-lg">Pocket journal, 48 pages.</p>
<button class="mt-4 inline-flex items-center px-4 py-2 rounded-md
bg-orange-700 text-white text-sm font-medium
hover:bg-orange-800 focus:ring-2 focus:ring-orange-700">
Add to cart
</button>
</article>
</main>
</body>
</html>---
Step 1 — Collect evidence
The Tailwind CDN ships no tailwind.config, so intent tokens are unavailable. Per Extraction Source Priority, fall back to framework defaults (the CDN's built-in scale) and mark every value as inferred in the final response. No custom webfont is loaded, so font-sans resolves to the system stack. This is the lowest-confidence evidence path — say so explicitly rather than presenting defaults as the site's authored design.
Step 2 — Resolve framework values
Map each utility class to its Tailwind v3 default (these are defaults, not authored tokens):
| Class | Resolves to | Token |
|---|---|---|
bg-stone-100 | #F5F5F4 | background |
text-stone-900 | #1C1917 | primary |
text-stone-500 | #78716C | secondary |
bg-white | #FFFFFF | surface |
bg-orange-700 | #C2410C | accent |
hover:bg-orange-800 | #9A3412 | accent-hover |
text-white | #FFFFFF | on-accent |
text-4xl / font-semibold / tracking-tight | 36px / 600 / −0.02em | h1 |
text-xl / font-semibold | 20px / 600 | headline |
text-lg | 18px / 400 | body |
text-sm / font-medium | 14px / 500 | label |
rounded-md / rounded-lg / rounded-xl | 6 / 8 / 12px | rounded.md/lg/xl |
py-2 / px-4 / p-6 / py-8 / gap-6 | 8 / 16 / 24 / 32 / 24px | spacing.sm/md/lg/xl |
rounded-lg (8px) appears only on images; rounded orphans are not linted, but the scale is kept for completeness.
Step 3 — Decide token granularity
Resist over-tokenizing. The page shows 7 roles, not 30 raw classes: primary/secondary/accent/accent-hover/background/surface/on-accent. Typography collapses to 4 steps (h1 / headline / body / label), not one per text class. Functional hover is a named token (accent-hover), never an inline value. Every color is referenced by ≥1 component so none orphan.
Step 4 — Write the frontmatter
---
version: "alpha"
name: Atlas Goods
description: Worked-example output for an e-commerce listing built on Tailwind CDN defaults.
colors:
primary: "#1C1917"
secondary: "#78716C"
accent: "#C2410C"
accent-hover: "#9A3412"
background: "#F5F5F4"
surface: "#FFFFFF"
on-accent: "#FFFFFF"
typography:
h1:
fontFamily: "ui-sans-serif, system-ui, sans-serif"
fontSize: 36px
fontWeight: 600
lineHeight: 1.1
letterSpacing: -0.02em
headline:
fontFamily: "ui-sans-serif, system-ui, sans-serif"
fontSize: 20px
fontWeight: 600
lineHeight: 1.4
body:
fontFamily: "ui-sans-serif, system-ui, sans-serif"
fontSize: 18px
fontWeight: 400
lineHeight: 1.6
label:
fontFamily: "ui-sans-serif, system-ui, sans-serif"
fontSize: 14px
fontWeight: 500
lineHeight: 1.25
rounded:
md: 6px
lg: 8px
xl: 12px
spacing:
sm: 8px
md: 16px
lg: 24px
xl: 32px
components:
page:
backgroundColor: "{colors.background}"
textColor: "{colors.primary}"
typography: "{typography.body}"
headline:
textColor: "{colors.primary}"
typography: "{typography.h1}"
caption:
textColor: "{colors.secondary}"
typography: "{typography.body}"
card:
backgroundColor: "{colors.surface}"
textColor: "{colors.primary}"
typography: "{typography.headline}"
rounded: "{rounded.xl}"
padding: "{spacing.lg}"
card-title:
textColor: "{colors.primary}"
typography: "{typography.headline}"
button-primary:
backgroundColor: "{colors.accent}"
textColor: "{colors.on-accent}"
typography: "{typography.label}"
rounded: "{rounded.md}"
padding: "{spacing.sm} {spacing.md}"
button-primary-hover:
backgroundColor: "{colors.accent-hover}"
textColor: "{colors.on-accent}"
---Step 5 — Write the rationale prose
Prose carries everything the whitelist cannot. Two decisions to copy as a pattern:
- Hover is a component variant, not prose.
button-primary-hoveris a separate component entry (backgroundColor: {colors.accent-hover}) — variant states belong in tokens when they have a fill change. - Focus ring is prose, not a token.
focus:ring-2 focus:ring-orange-700has no whitelisted sub-token, so it lives in the Components section as description ("focus shows a 2px accent ring"), never as aborderColor/ringkey (which would warn as unrecognized). - Responsive collapse is Layout prose.
grid-cols-2→ single column undermdis described in## Layout; breakpoints are not frontmatter tokens (nolayoutkey; unitless spacing is ignored). - Image aspect ratio is prose. The 4:3 framing (
h-40full-width) is noted under Shapes, sinceaspectRatiois not a recognized sub-token.
The eight canonical sections follow in order: Overview, Colors, Typography, Layout, Elevation & Depth, Shapes, Components, Do's and Don'ts.
Step 6 — Validate
npx -y @google/design.md lint DESIGN.md
# → exit 0, 0 errors, 0 warnings (1 info summary: 7 colors, 4 typography, 3 radius, 4 spacing, 7 components)The on-accent / accent pair clears WCAG AA at ~5.2:1, so no contrast-ratio warning. Every color is referenced, so no orphaned-tokens warning.
---
Key decisions, explained
- Why a `page` component? The
backgroundcolor must be referenced by a component or it warns as orphaned. The page canvas is a real component, so it earns the token rather than being a workaround. - Why `accent-hover` as a color, not just a component override? Hover fills recur (buttons, links, nav); a named color token lets every hover variant reference one source of truth and keeps the palette auditable.
- Why is the whole result marked `inferred`? The CDN carries no authored config. The final response to the user must state that these are Tailwind defaults, not the site's own design tokens — source-faithful means naming the limitation.