Use when converting a UI reference (screenshot/design) into Polaris React, matching a design pixel-for-pixel, or verifying built UI against a target. Triggers on "make it match", "exact match", "build this UI from this screenshot/design", or when a previous attempt was "close but not exact". Renders in an isolated sandbox at localhost:6090 that loads YOUR project's Polaris, measures the reference, and asserts the rendered DOM against those numbers via Playwright.
How this skill is triggered — by the user, by Claude, or both
Slash command
/polaris-visual-sandbox:polaris-visual-sandbox [path to UI reference image] [target file: the real component/page to apply the result to][path to UI reference image] [target file: the real component/page to apply the result to]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Produce a **pixel-exact** Polaris React build of a reference and prove it by
Produce a pixel-exact Polaris React build of a reference and prove it by measuring the reference and asserting the rendered DOM against those numbers — not by eyeballing a screenshot. "Looks close" is failure. The target is exact: padding, margins, radius, shadows, alignment, color, font size and weight, optical alignment, and every interactive state.
Pairs with polaris-design (how to author idiomatic Polaris). This skill adds the
strict measure → build → assert → iterate loop and the direct-to-target delivery.
The sandbox runs outside your project (from a cache dir) but renders your
project's exact @shopify/polaris + React — start-sandbox.sh discovers your
project's node_modules and aliases Vite to it, and tokens.py reads the same
Polaris stylesheet. So the numbers match what your app actually ships. Nothing is
scaffolded into your repo.
$S = ${CLAUDE_PLUGIN_ROOT}/skills/polaris-visual-sandbox (this skill's dir).
Scripts live at $S/scripts/.start-sandbox.sh and tokens.py both discover Polaris from there.You may NOT say "match", "done", "faithful", or "looks right" until DOM assertions pass against numbers you measured from the reference. A screenshot that looks correct is not evidence — the chevron-centering, wrong-color, and dropped-token bugs all pass the eye test and fail the DOM. Violating the letter of this (skipping one assertion "because it obviously matches") is violating the spirit. If a number is unverified, you are not done.
Every pixel, color, and token comes from a tool, never from memory:
| You need | Use |
|---|---|
| A Polaris token's real value (px/hex) | $S/scripts/tokens.py — reads the Polaris build YOUR project loads |
| Which token a measured value maps to | tokens.py nearest-space <px> / nearest-color <hex> |
| A measurement off the reference image | $S/scripts/measure.py (dims, color, bbox, edges, grid, compose, diff) |
| The rendered value of your build | mcp__playwright__browser_evaluate → getBoundingClientRect + getComputedStyle |
| A component's props / valid values | context7 MCP (resolve-library-id → @shopify/polaris → query-docs) |
| Quick token/recipe/color map | $S/polaris-reference.md (then confirm exact values with tokens.py) |
Guessing a number is the #1 cause of "close but not exact". An off-grid spacing
token (e.g. 250, 350) is silently dropped to 0 — tokens.py nearest-space
catches it before you ship it.
useState) so the output is clickable — Popovers open,
tabs switch, inputs focus, accordions expand.t() in the sandbox.
Local component state for interaction is encouraged.@shopify/app-bridge-react components (NavMenu, TitleBar, App Bridge
Modal) — use Polaris equivalents for the visual.mcp__playwright__browser_*). If not, ask
the user to enable it, then retry. (DOM assertion and interaction checks require it.)node + npm on PATH (the sandbox's build tooling installs on first run).python3 with PIL (python3 -c "import PIL"). The skill's scripts need it.@shopify/polaris + react installed. The sandbox
renders those exact versions.From your project root:
"$S/scripts/start-sandbox.sh"
(If $S isn't expanded in your shell, use the literal
"${CLAUDE_PLUGIN_ROOT}/skills/polaris-visual-sandbox/scripts/start-sandbox.sh".)
It prints one JSON line, e.g.:
{"type":"sandbox-started","url":"http://localhost:6090","port":6090,
"slot_path":"/Users/you/.cache/polaris-visual-sandbox/Slot.tsx",
"node_modules":"/path/to/project/web/frontend/node_modules",
"polaris_styles":".../@shopify/polaris/build/esm/styles.css","polaris_version":"13.9.5"}
Save slot_path — that's the file you edit in Step 3. Confirm polaris_version
matches your app. If the script can't find Polaris, pass
--node-modules <path/to/node_modules>. If your shell reaps background processes,
run it with the Bash tool's run_in_background: true (it self-backgrounds anyway).
This is where exactness is won. Do NOT start building from a glance.
Read the reference image.python3 "$S/scripts/measure.py" edges <ref> <x0> <y0> <x1> <y1> across a known 1px hairline. If it's ~2px wide / controls look ~2× the reference tables, the image is 2× — divide every measurement by 2. Record imagePxPerCssPx.$S/polaris-reference.md).measure.py bbox / edges;tokens.py nearest-... / text → the variant (remember bodyMd=13px, bodyLg=14px);measure.py color → tokens.py nearest-color → the token;tokens.py nearest-space must say EXACT).Run tokens.py from your project root so it reads your app's Polaris.
Write Polaris JSX into the slot_path from Step 1 (the cache-dir Slot.tsx):
default export, hard-coded content, real useState for interactions.
Initialize the state you're matching to its target (e.g. useState(true) for an
open Popover) so HMR always renders that state while it stays clickable. Use only
the tokens you confirmed in Step 2. Confirm exact props via context7 /
polaris-design. Reuse the recipes in $S/polaris-reference.md (Select-style
trigger, inset hover band) instead of rediscovering them.
browser_navigate → http://localhost:6090 (first iteration only; HMR repaints after).browser_resize → reference width ÷ scale.browser_click/browser_hover (use browser_snapshot to find refs).browser_take_screenshot. Blank/broken? browser_console_messages → fix → retry.For each spec element, browser_evaluate getBoundingClientRect() +
getComputedStyle() (snippet in $S/polaris-reference.md). Assert against Step-2:
550=medium, 450=regular);icon.right ≈ container.right − paddingRight; do not trust justify-content: space-between (it centers a lone child while reading "correct").Any mismatch → edit the slot, re-screenshot, re-assert. Loop. List the failing numbers explicitly each round.
Exercise every interaction with Playwright and assert each resulting state, so the UI actually responds (not just static pixels):
browser_click the trigger → Popover/menu opens; click outside → closes.browser_hover a row → hover band appears with the exact spec color/inset.browser_type / browser_press_key / browser_select_option for inputs, focus rings, selects.
Screenshot + assert each interactive state against the reference's matching state.Done only when: every Step-5 assertion passes AND a scale-normalized
overlay shows no structural drift (measure.py compose <ref> <shot> out.png and
measure.py diff — the diff's changed-region should be noise, not structure)
AND every Step-6 interaction behaves. Then, and only then, report "match" —
citing the asserted numbers.
Apply the matched UI directly to the target file (from the argument, or ask once if unknown). Do not paste the final JSX into chat as the deliverable.
Edit/Write the real component/page file in YOUR project with the matched structure.t() if your app uses i18n, wire hooks — follow polaris-design for placement and the surrounding file's conventions.pnpm run pre-commit, or your equivalent)."$S/scripts/stop-sandbox.sh" --reset # stops the server and restores the placeholder
Or keep the server running for the next task and just reset the slot file by
copying the placeholder back (Slot.placeholder.tsx, next to slot_path).
measure.py + tokens.py250/350 vanish)| Excuse | Reality |
|---|---|
| "It's visually a faithful match" | Visual gestalt is exactly what shipped the chevron/color/size bugs. Assert the DOM. |
| "justify-content is space-between, so it's pinned" | It centered a lone child at x=860 in a 699–1009 box. Measure icon.right. |
| "This looks like ~14px" | bodyMd is 13px, bodyLg is 14px. getComputedStyle decides, not your eye. |
| "Gray hover, I'll use bg-surface-selected" | That's neutral too, but the blue one is bg-surface-emphasis. Sample, then nearest-color. |
| "paddingBlock 250 is about right" | 250 isn't a token → 0 padding → collapsed rows. tokens.py nearest-space 10. |
| "The reference is small, I'll estimate" | Estimating at thumbnail scale sends you down wrong paths. Run the scale check. |
| "I'll show the user the code to paste" | The deliverable is the applied change. Edit the real file. |
$S/polaris-reference.md — token tables (verified), color map, recipes, DOM-assert snippet, states checklist, worked example.$S/scripts/tokens.py / $S/scripts/measure.py — run with -h for usage.$S/scripts/start-sandbox.sh / stop-sandbox.sh — boot/stop the isolated sandbox.polaris-design skill — idiomatic Polaris authoring patterns.npx claudepluginhub mrmarufpro/polaris-visual-sandbox --plugin polaris-visual-sandboxGuides test-driven development for Django applications using pytest-django, factory_boy, and Django REST Framework. Covers red-green-refactor workflow, conftest fixtures, and coverage reporting.