Design parity review
Prove a Compose UI pull request matches its intended design by diffing the rendered code (candidate) against a Figma / Stitch / Claude Design reference and posting a parity verdict. Use when asked to check a PR against a design, wire up a design-parity bot, set up the committed reference cache so runs make zero Figma calls, shard an exhaustive catalog check across parallel jobs, map code components to design nodes via design-map.json or Code Connect, or push code renders back onto the Figma canvas. This is the design→code direction; for code→design catalogs see compose-design-catalog and figma-catalog-import.From its SKILL.md
npx -y skills add yschimke/skills --skill design-parity-reviewAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.2 KB, as published. Nobody here has run it
Design parity — reviewing code against its design
design-parity is a tool-neutral bot that proves a UI pull request is at
parity with its intended design. On a UI PR it:
- resolves which design reference matches the changed component,
- renders the new code — the candidate — via
compose-preview, - diffs candidate vs reference (visual + semantic + token),
- posts a verdict in the PR — e.g. "implements Figma
Button/Primary; padding 12dp vs spec 16dp; dark-theme contrast fails AA."
The candidate side is the upstream compose-preview renderer (the
compose-preview skill). What design-parity
owns is the reference side and the correspondence layer that decides
which design maps to which code.
Source
Maintained at github.com/yschimke/skills
under skills/design-parity-review/. The tool ships from
yschimke/design-parity and on npm
as design-parity — run it with
npx design-parity …, no checkout needed. That repo's
docs/README.md
indexes the full contracts; when it disagrees with this skill, it wins.
Which direction is this?
The two directions are different tools and it is worth being explicit, because picking the wrong one wastes a lot of setup:
| You want | Direction | Skill |
|---|---|---|
| "Does this PR's code match the Figma design?" | design → code | this skill |
| "Publish our component system into Figma as a sticker sheet" | code → design | compose-design-catalog → figma-catalog-import |
| "Is the rendered UI different from last commit?" | neither — no design involved | compose-preview-review |
Round-tripping — running both directions on one project — is supported and covered in references/round-trip.md. Read the direction policy section below first: which side is canonical is a committed decision, not a per-run choice.
Set-up order
Do these in order. Each step is useless without the one before it.
1. Decide the direction (.design-parity.json)
Parity has a committed direction. It decides who wins when the two sides disagree, and therefore whether a failing check blocks the PR.
{
"$schema": "https://github.com/yschimke/design-parity/raw/main/packages/policy/schema/parity-config.schema.json",
"direction": "code-led"
}
design-led— the design is canonical. A parity failure blocks. Right for a team with a maintained design system.code-led— the code is canonical; the design is a reference that may lag. Findings are reported but don't block. Also the precondition for pushing code renders back onto the canvas.auto— resolved deterministically by@design-parity/policyfrom the repo's maturity.
2. Map code to design (design-map.json)
Correspondence is resolved in this order: Code Connect (Figma only) →
committed design-map.json → name convention with a low-confidence
flag. Anything but the first two is a guess; prefer to commit the map.
{
"components": [
{ "code": "ui/Button.kt#PrimaryButton", "source": "figma",
"ref": "figma:AbCdEf123456/1:42" },
{ "code": "ui/Card.kt#OfferCard", "source": "claude-design",
"ref": "design/reference/offer-card.html" },
{ "code": "ui/Device.kt#DeviceScreen", "source": "figma",
"ref": [
{ "ref": "figma:AbCdEf123456/10:2", "state": "default" },
{ "ref": "figma:AbCdEf123456/10:8", "state": "error" },
{ "ref": "figma:AbCdEf123456/10:9", "theme": "dark" }
] }
]
}
One code handle can bind to several design nodes keyed by state /
theme. npx design-parity reverse figma:AbCdEf/1:42 answers the opposite
question — which code implements this design node — for sources without Code
Connect.
If a repo has no committed design-map.json, the action posts a one-time
notice pointing at the interactive bootstrap (design-parity-bootstrap)
rather than guessing the mapping at run time, and never blocks. Don't
hand-author a map for a project that hasn't run bootstrap — bootstrap also
materialises tokens and tuned check thresholds.
A Compose catalog is the exception, and doesn't need bootstrap for the map.
If its components already carry @CatalogComponent(reference = …), the map is a
projection of those annotations — derive it rather than authoring or
bootstrapping it, and the multi-node bindings above come out of the same
projection instead of being typed by hand. See Deriving the map from
annotations.
3. Pick a reference source
| Source | Auth | Notes |
|---|---|---|
claude-design | none | Commit the HTML export per screen; rasterized headlessly. Fully offline and deterministic — best fit for a first adoption. |
figma | FIGMA_TOKEN | REST + Code Connect. The only source with automatic correspondence. Rate-limited — see the cache below. |
stitch | yes | stitch:<projectId>/<screenId> (two parts; a single-part ref is rejected). Needs the SDK and a headless Chrome. |
bundle | none | A committed directory or .zip of reference PNGs + manifest.json. No design-tool API at all. |
4. Render candidates
Parity asserts candidate render ≈ what ships, so render on a target that represents the shipped UI. For Compose Multiplatform, the Desktop/JVM target renders with no Android emulator — the cheapest path, and the reason CMP is the recommended way to try the tool.
Two traps specific to this step:
- Theme. CMP apps usually theme via
MaterialTheme/ aCompositionLocal, so the Android night-modeuiModeis unset and the candidate gets notheme— which then fails to pair with atheme-tagged reference. Theme is derived in precedence order: an explicitthemehint on the preview (set this when theming via aCompositionLocal), then the AndroiduiMode, then the preview id's trailing_Dark/_Light/Nighttoken (only the last token, soHome_LightOnis not mistaken for light). - Semantics. Make sure the bundle carries the semantics blob (a11y tree
- resolved fg/bg colours + typography). With it, the full a11y/i18n + contrast + token checks run; without it they degrade silently to visual/structural-only.
Platform-specific UI (Android-only APIs, actual impls, Android resources)
won't match a Desktop render. Render those on Android, or lift the screen's
composable into commonMain.
5. Wire the reference cache — before you wire the run
Do this before the parity workflow, not after. Skipping it is the single most damaging mistake in a real adoption.
A run that fetches every reference live pays the full reference cost on every
commit, against a per-token rate limiter. Observed on a 77-component
catalog: 18 components produced a verdict and 59 reported
figma: rate limited (429) — and a run covering a quarter of the catalog
looked exactly like one covering all of it, with a different quarter each
run.
The fix is to split the two sides by cadence. See references/reference-cache.md.
6. Wire the parity run
.github/workflows/design-parity.yml, calling the reusable workflow — see
references/ci.md for the sharded/exhaustive setup and
the standalone action form.
Reading a verdict
Findings are ordered by what actually matters, not by pixel count: a11y + i18n first, then tokens, then pixels. A 3px shift with passing contrast is a lower-priority finding than a dark-theme contrast failure that diffs to almost nothing.
On a push to the development branch the action publishes browsable
artifacts to a permanent design-parity/<dev-branch> branch: a landing
index.html, each component's self-contained report.html triptych
(reference | candidate | diff), a machine-readable verdict.json, and — when
the run exposes design-system tokens — the aggregated DTCG table at
tokens/design-system.tokens.json.
History accrues automatically: each run re-parents on the branch tip (a
linear chain, fast-forward push, no force), and because report.html is
deterministic a run that doesn't change a screen touches no file and adds no
commit noise. The history therefore shows exactly the runs where a screen's
code or mock actually moved.
Reference docs
| Path | When to read |
|---|---|
| references/reference-cache.md | Importing the design side on its own schedule into a committed cache so the parity run makes zero Figma calls. Why the coupled version silently reported on a quarter of a catalog. |
| references/ci.md | The two reusable workflows (import + run), scoping the render, and sharding an exhaustive check across N jobs. |
| references/round-trip.md | Running both directions on one project: catalog export out to Figma, parity checks back in, and the opt-in Code-to-Canvas push-back. What must agree between them. |
Related
- compose-preview — the candidate renderer.
- compose-design-catalog / figma-catalog-import — the code→design direction.
- compose-preview-ci — the sibling CI surface for plain preview diffs (no design reference involved).