Compare a webpage or UI layout against a Figma design, then guide the agent to build or fix the implementation. Scripts handle capture, comparison, and reporting; the agent applies layout fixes based on Figma data and diff results. Use when the user provides a Figma URL and asks to build, recreate, match, compare, restyle, or tighten implementation to that design.
Use this skill when a user shares a Figma link and wants to build a page from that design or bring an existing implementation closer to it. Treat Figma as the source of truth.
This skill has two layers:
The scripts do not auto-patch code. They produce the data and artifacts the agent needs to make accurate, Figma-grounded fixes.
This skill should behave in a production-ready way:
FIGMA_TOKENfigma-pixel-runs/ directory (i.e. alongside this SKILL.md file)Prerequisites:
FIGMA_TOKEN must be setInstall these packages in the host environment before using the skill:
npm install playwright pixelmatch pngjs @techstark/opencv-js --save-prod
npx playwright install chromium
On Linux, Chromium may also require system libraries:
apt-get update && apt-get install -y libnspr4 libnss3 libatk1.0-0 libatk-bridge2.0-0 libx11-xcb1 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 libpangocairo-1.0-0 libgtk-3-0
This skill does not install dependencies at runtime. Without these packages, system libraries, and a working browser executable, the skill will not work fully and some flows will fail immediately. If required packages are missing, stop and report the missing dependency clearly.
Read references/setup.md for environment expectations.
Never run the pipeline (or any comparison/capture script) without explicit user instruction. Each pipeline run must be triggered by the user — do not auto-run after fixes, do not run "to verify", do not run multiple times in a row without asking between each.
Use scripts/run-pipeline.cjs as the primary orchestration entry point.
Prefer the pipeline over one-off script combinations unless you are debugging a specific failing stage.
Token discipline for this skill:
run-result.json, pipeline-summary.json, final/report.json, figma/viewport.json, figma/export-image-result.json.scripts/parse-figma-url.cjs to extract file key and node id.scripts/fetch-figma-api.cjs to fetch file and node data when API access is available.Read references/figma.md for the expected Figma input layer.
scripts/init-run-dir.cjs.figma-pixel-runs/<project-slug>/<run-id>/ directory — this folder lives next to SKILL.md, not inside the implementation project.figma-pixel-runs/<project-slug>/shared/figma/ so repeated runs for the same file/node do not refetch the same Figma file, node, and reference image every time..figma-source, temporary export caches, or other non-project runtime files. Keep working files in the run directory or shared cache only.<img>. Do not attempt to hand-code such nodes. The Figma images API endpoint is GET /v1/images/{fileKey}?ids={nodeId}&format=png&scale=2. Save exported PNGs to the implementation project's public/static directory and reference them directly. This is the single most impactful action for pixel accuracy — hand-coded approximations of UI mockups will always diverge from the Figma reference.Read references/artifacts.md for the expected artifact set.
After fetching Figma data, extract the list of unique font families used in the design.
Read fontFamily values from figma/design-tokens.json (typography array) if available, or from figma-node.json directly.
First classify each font as system or non-system — do not import system fonts. System fonts ship with the OS; adding <link> / @import for them does nothing on the target platform but may trigger unnecessary network requests and makes the CSS misleading. Only non-system fonts need to be connected.
For system fonts, reference them directly in a font-family stack with appropriate OS-level fallbacks. When a design splits a single family into display/text variants, prefer a single font variable with the display family rather than two CSS variables — unless the user explicitly asks for the split.
For non-system fonts, automatically connect them without asking the user. Do not stop or prompt — just add them.
<link rel="preconnect"> + <link rel="stylesheet"> for Google Fonts (or the appropriate CDN) to the implementation's HTML <head>.@import url(...) at the top of the main stylesheet.document.fonts.ready, so no extra delay is needed.If you cannot confidently classify a family as system vs non-system (obscure corporate font, custom foundry name, etc.), ask the user once rather than guessing — importing the wrong family or missing one both cause layout reflow.
After fetching Figma data and before writing any HTML/CSS, run:
node scripts/extract-implementation-data.cjs <path-to-figma-node.json>
This produces implementation-spec.json in the same directory as figma-node.json.
When using run-pipeline.cjs, this file is generated automatically — check artifacts.implementationSpec in the run result.
The spec gives you in one file:
viewport — exact frame dimensions (width × height)sections[] — full annotated node tree with bounds (relative to root 0,0), fill, stroke (including individualStrokeWeights + dashPattern when present), cornerRadius, layout (auto-layout mode/padding/gap), effectstexts[] — flat list of every text node with characters, style, and (when present) styledRuns[]fonts[] — unique font families usedcolors[] — all fill colors sorted by frequency, as { hex, count }warnings[] — nodes with visible=false, invisible fills, and TEXT nodes with inline style overridesUse implementation-spec.json as the primary reference when building or fixing layout. Avoid repeated ad-hoc queries against the raw figma-node.json — the spec captures everything needed in a single structured pass.
Read warnings[] immediately — before writing a single line of HTML or CSS. Every entry is either a hidden node, an invisible fill, or a TEXT node that contains inline style overrides. Do not skip this check.
Check styledRuns[] on every TEXT entry before writing markup. When a texts[] entry has styledRuns, the text contains mixed styling — bold spans, colour changes, or font-weight overrides. Render each run using the appropriate inline element (<strong>, <em>, or <span>) with styles derived from styledRuns[].style. Ignoring styledRuns produces flat-weight text that causes layout reflow and pixel mismatches. Each run: { start, end, characters, style } where style is already the merged result of the base style and the Figma override.
Before writing or editing ANY text-bearing CSS, read typography-map.json. run-pipeline.cjs generates it next to implementation-spec.json (path under artifacts.typographyMap). It is a deduped list of every unique text style in the design — each entry has fontFamily, fontSize, fontWeight, fontStyle, lineHeightPx, letterSpacing, color, occurrences, and samples. Use these values verbatim for every text rule. Do not eyeball typography from the reference image, do not default to "none" on letter-spacing or 1 on line-height. Every one of the six typography fields — family, size, weight, style, line-height, letter-spacing — must come from typography-map.json.
Before writing or editing ANY margin / padding / gap, read spacing-map.json. run-pipeline.cjs generates it next to implementation-spec.json (path under artifacts.spacingMap). It is a flat list of every auto-layout container with its mode, paddingTop/Right/Bottom/Left, itemSpacing, and bounds, plus a summary of unique gap and padding values used in the design. Each entry has a path breadcrumb (e.g. Section > Content > Texts) so you can locate the exact Figma node for any container you render. Cite a concrete entry when picking a spacing value — if no auto-layout node matches the element you are styling, that is a blocker, not an invitation to guess.
Before writing or editing ANY border / outline / divider CSS, read strokes-map.json. run-pipeline.cjs generates it next to implementation-spec.json (path under artifacts.strokesMap). It is a flat list of every node with a visible Figma stroke — each entry has path (breadcrumb), bounds, color, weight, align, sides[] (which of top/right/bottom/left are active), perSideWeight, dashPattern, and cornerRadius/cornerRadii, plus a summary of unique colors, weights, and side-distribution. Partial strokes (single-side borders like a header's bottom divider or a footer's top divider) are the most commonly missed property — Figma expresses them via individualStrokeWeights, and they render as thin horizontal or vertical lines that are easy to dismiss as diff artifacts. Every border* / outline* / horizontal or vertical divider in CSS must trace to an entry here. If a section header or footer appears in strokes-map.json with sides: ["bottom"] or sides: ["top"], that is a required border-bottom / border-top — do not treat it as optional decoration. For light backgrounds Figma usually uses a dark stroke color and vice versa; always read color from the entry rather than guessing from the section background.
To generate these manually (if running scripts individually instead of via the pipeline):
node scripts/extract-typography.cjs <path-to-implementation-spec.json>
node scripts/extract-spacing-map.cjs <path-to-implementation-spec.json>
node scripts/extract-strokes-map.cjs <path-to-implementation-spec.json>
Read references/scripts.md for the exact argument format and output contract.
Skip this step if an implementation already exists — go directly to Step 4.
If no implementation exists yet:
implementation-spec.json (from Step 2c) for frame bounds, layout structure, colors, typography, spacing, and component hierarchy.package.json, framework config files (next.config.*, vite.config.*, nuxt.config.*, etc.), or ask the user if unclear.Prefer the most stable path that avoids unnecessary build-tool churn.
Use this order:
Do not get stuck debugging Vite, bundlers, or optional native dependencies unless the user explicitly wants pipeline repair. If the page can be served statically, prefer that.
scripts/render-page.cjs for deterministic capture.If browser lookup is flaky, use CHROMIUM_PATH or PLAYWRIGHT_MODULE_PATH as explicit overrides.
Use the available comparison tooling to produce a reliable visual diff.
Prefer the existing scripted comparison flow.
Use Playwright render capture and pixelmatch as the primary reliable comparison path.
When available, run the optional Node.js post-processing step after pixelmatch to group raw pixel differences into larger mismatch regions.
Always try to produce these artifacts:
run-pipeline.cjs runs a tile comparison automatically (300px horizontal bands) and writes pixelmatch/tile-report.json. Read tileCompare.topMismatchTiles from the run result to identify the highest-mismatch vertical zones first. Each tile entry includes sectionName and sectionRelativeY — use these to know which section to inspect without manually dividing by section height.
Visually inspect top mismatch tiles before looking at the full-page diff. For each top tile, crop the reference, screenshot, and diff images at the exact tile y-coordinates and read them with the Read tool. Use scripts/crop-tile.cjs for precise cropping (platform crop tools like sips use unreliable coordinate systems):
node scripts/crop-tile.cjs <src.png> <dst.png> <y> <height>
Tile-level inspection identifies the actual cause of each mismatch zone — missing borders, wrong icon direction, layout shift from text height differences — rather than guessing from the full-page view.
After inspecting the top tiles, read all three full-page images (reference, screenshot, diff) using the Read tool for a structural overview. Full-page inspection catches issues that span multiple tiles: wrong section count, missing sections, wrong page height, or broad color/background mismatches.
OpenCV runs per tile on the top 3 highest-mismatch bands (not on the full image), so it works correctly for full-page designs of any height without WASM memory issues. Each reported region includes tileY (the tile's absolute Y offset) and y (absolute Y coordinate in the full image).
After each run, run-result.json is written to the run directory as soon as pixelmatch and tile comparison finish — before OpenCV. Read it first: it contains mismatch (diffPercent), delta (change vs previous run), tileCompare.topMismatchTiles, and artifacts paths. If the pipeline crashes after tile comparison, run-result.json still exists with the essential data.
This step is performed by the agent, not by the scripts. The agent uses Figma API data, reference images, and diff reports from previous steps to decide what to change, then edits the implementation files (HTML, CSS, assets) directly.
Before making fixes, read tileCompare.topMismatchTiles from run-result.json to identify the highest-mismatch vertical zones. Each tile includes y (absolute pixel offset), sectionName, and sectionRelativeY. Fix the highest-mismatch sections first, then move to lower-mismatch areas.
Prioritize the biggest contributors first:
Prefer visible, direct fixes over refactors.
Do not implement nodes where visible === false in the Figma API — these are hidden layers and must be skipped entirely. Check visible on every node before using its content, fills, or dimensions.
Do not invent new content if the design already defines it.
Do not replace real preview visuals with invented placeholders when Figma already provides the real screenshot or crop source.
Use Figma API data and screenshots to ground spacing, sizing, structure, embedded preview imagery, color decisions, corner radius, borders, effects, and typography.
Do not invent page, section, card, or preview colors when the Figma file already defines them. Read and match section backgrounds, text colors, fills, borders, and accents directly from the Figma source.
Matching colors directly from Figma can materially reduce mismatch and should be preferred over manual palette guessing.
Read and apply cornerRadius or rectangleCornerRadii from Figma for cards, buttons, inputs, images, and preview panels instead of defaulting to generic border radius values.
Match typography from Figma, including font family, font size, font weight, line height, letter spacing, and text alignment.
Match borders and visible effects from Figma, including stroke width, stroke color, shadow, blur, and opacity when they materially affect the rendered result. Consult strokes-map.json for every border decision — including partial/single-side strokes (header bottom dividers, footer top dividers, card outlines). A thin horizontal line visible in the diff is almost always a missing border-bottom / border-top from a node with individualStrokeWeights set on one side; do not dismiss such lines as antialiasing or rendering artifacts.
Use the correct Figma-derived assets for images, thumbnails, screenshots, and fills. If an asset cannot be extracted from Figma, report the blocker clearly instead of silently substituting an incorrect image.
Prefer exact layout dimensions from Figma bounds over approximate CSS values. Avoid "close enough" sizing when the design provides exact measurements.
After each pass, read run-result.json first — it is the single compact source of truth for a run:
mismatch — diffPercent for this rundelta — change vs the previous run (negative = improvement)previousRun — { runId, diffPercent } of the run before this onetileCompare.topMismatchTiles — ranked zones to fix next, each with sectionName and sectionRelativeYartifacts.* — paths to all generated filesSummarize:
When checking prior results, prefer run-result.json over rereading large markdown or raw tool logs.
final/report.json and final/summary.md are generated by run-pipeline.cjs automatically. If they are missing (pipeline crashed before reaching that step), run:
node scripts/generate-layout-report.cjs \
--output <run-dir>/final \
--figma <figma-url> \
--page <page-url> \
--viewport <WxH> \
--reference <run-dir>/figma/reference-image.png \
--screenshot <run-dir>/capture/captured-page.png \
--diff <run-dir>/pixelmatch/diff.png \
--pixelmatch-report <run-dir>/pixelmatch/report.json
If tooling failed but useful artifacts exist, say so plainly and continue with the best available diff method.
If the page is unreachable, FIGMA_TOKEN is missing, or required artifacts cannot be produced, stop and report the blocking reason clearly.
At the end of the task, ask the user whether they want to clean up working files under figma-pixel-runs/<project-slug>/ before deleting anything.
After summarizing results, stop. Do not ask whether to run another iteration and do not start one automatically.
If the user explicitly requests another pass (e.g. "run again", "fix more", "next iteration"), return to Step 6 and repeat from there. Otherwise the task is done.
When this skill is used, always try to return:
Before considering the task done, verify this fidelity checklist:
spacing-map.json; do not mark done if any value is eyeballed or picked as a round defaultfontFamily, fontSize, fontWeight, fontStyle, lineHeightPx, letterSpacing) against typography-map.json for each text style used; do not mark done if any field is eyeballed or defaultedfigma-pixel-runs/ may contain sensitive UI content. Review and clean these folders before sharing or committing them.references/figma.md for the Figma input layer.references/workflow.md for a concise execution checklist.references/artifacts.md for the run directory contract and expected artifact outputs.references/scripts.md for the exact CLI usage of every script, including extract-implementation-data.cjs.