Figma plugin · Chrome extension · CLI · MCP

Pixel-perfect QA
against Figma designs

Compares a live page with its design element by element — computed CSS and geometry, not screenshots. A companion plugin reads the file from inside Figma: no REST API, no rate limits.

pixel-guard · home @ desktoplocalhost:8971 ⇄ figma 994:13213
Furniture · est. 2019

Furniture that outlives trends

Solid oak, hand-finished in small batches. Delivered flat, assembled in minutes.

Browse the catalogue
Furniture · est. 2019

Furniture that outlives trends

Solid oak, hand-finished in small batches. Delivered flat, assembled in minutes.

Browse the catalogue
designbuild
0Figma REST calls — the plugin reads from inside Figma
1 fileper page: nodes, styles, geometry in snapshots/
3breakpoints per run via CDP, window untouched
exit 1on any mismatch — CI-ready as is
What it does

Everything DevTools and a ruler do — automated

Every element is checked against its Figma node: typography, colours, spacing, size and position. Differences come back named, not as a percentage of pixels.

Element-by-element

Computed CSS and geometry of each mapped element against the node in the snapshot. Per-property tolerances, per-node ignores.

No REST API in the loop

The Figma plugin walks the file through the Plugin API and writes a JSON snapshot. No tokens, no 429s — works with any file you can open.

Figmaplugin · Capture
snapshot.jsonnodes · styles
CLIqa · automap · patch
Chrome paneloverlay · check
MCPClaude Code

Three breakpoints

Desktop, tablet and phone through CDP device emulation. The browser window never resizes.

Agent-native

An MCP server exposes node styles, renders and the checker. Claude Code fixes CSS without a Figma tab.

› figma_get_node_styles hero/title
‹ font-size 56px · weight 700 · lh 1.1
› run_qa --page home
✕ 1 mismatch · .hero h1 font-size
› edit styles.css:41 → 56px
✓ 42/42 match

CSS patch from diffs

npm run patch turns mismatches into a proposed patch you review before applying.

/* home @ desktop */
.hero h1 {
- font-size: 52px;
+ font-size: 56px;
}

Design overlay & curtain

Rendered PNGs of design blocks laid pixel-aligned over the page. Drag the curtain: design on one side, the live page on the other — misalignments jump out.

title ↑ 3px
designpage

Maps you can read

A node ↔ selector map per page, shared blocks once. automap writes most of it; verify --fix catches selectors that stopped matching.

"hero/title": { "selector": ".hero h1" },
"hero/lead":  { "selector": ".hero p", "tolerance": { "line-height": 0.05 } },
"hero/badge": { "skip": "rendered by JS" },
"@header":    "_shared"
How it works

Figma once. Everything else from disk.

The snapshot is a plain JSON you can commit. CLI, browser panel and the MCP server all read the same file.

01

Capture

Select frames in Figma, press Capture — or export the whole project.

✓ snapshots/home.json214 nodes
✓ snapshots/_project.json12 pages
02

Map

automap pairs nodes with selectors; the rest is a click.

hero/title↔.hero h1
hero/cta↔.hero .btn
03

Check

CLI, browser panel or MCP — the same report.

✓ 41 properties match
✕ .hero h1 font-size52 → 56px
Output

One run, three readers

An HTML page for people, JSON for agents, and a CSS patch proposal.

node → selectorpropertyfigmaactual
hero/title → .hero h1font-size56px52px
hero/cta → .hero .btnpadding14px 28px12px 24px
@header/nav → .navgap32px24px
hero/lead → .hero pcolor#a9b0bd#a9b0bd
hero/lead → .hero pline-height1.551.55
{
  "page": "home", "viewport": "desktop", "status": "failed",
  "diffs": [
    { "node": "hero/title", "selector": ".hero h1", "prop": "font-size", "figma": "56px", "actual": "52px" },
    { "node": "hero/cta", "selector": ".hero .btn", "prop": "padding", "figma": "14px 28px", "actual": "12px 24px" }
  ],
  "matched": 41, "missing": [], "skipped": ["hero/badge"]
}
/* pixel-guard patch · home @ desktop · review the cascade before applying */
.hero h1   { font-size: 56px; }       /* was 52px */
.hero .btn { padding: 14px 28px; } /* was 12px 24px */
.nav       { gap: 32px; }             /* was 24px */
Why not screenshots

Same bug, two reports

A heading rendered at 52px instead of 56px.

Screenshot diff any pixel tool
3.4 % pixels differ
  • 3.4 % of pixels differ — a number, not a cause
  • Everything below the heading shifted, so the whole block lights up
  • Anti-aliasing and hinting add noise on every run
  • To fix it: open DevTools, open Figma, measure by hand
pixel-guard per-element
hero/title · font-size 52px → 56px
  • 1 mismatch, named: node, selector, property, both values
  • Downstream layout shifts are not reported twice
  • Deterministic — same DOM, same report
  • The fix is one CSS line, no Figma needed
Install

Up in a minute

Node ≥ 20, Chrome, Figma desktop app for snapshots.

$ git clone https://github.com/PavelKhabusov/pixel-guard && cd pixel-guard
$ npm install
$ ./start.sh            # Figma ↔ browser bridge + panel

# Figma  → Plugins → Development → Import plugin from manifest… → plugin/manifest.json
# Chrome → chrome://extensions → Load unpacked → extension/
More by me