npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@artcom/figma-visual-parity

v0.2.0

Published

Measure whether a rendered implementation matches its design reference, and diagnose why it does not.

Readme

@artcom/figma-visual-parity

Measure whether a rendered implementation matches its design reference — and when it does not, say why.

Built for the design-to-code loop: verify is a programmatic verdict you can gate on, explain runs an ordered diagnosis so the first thing you read is the thing worth fixing. A bare "3 % of pixels differ" tells you nothing about what to change.

npm i -D @artcom/figma-visual-parity
npx playwright install chromium     # or point FIGMA_VISUAL_PARITY_CHROMIUM at a binary

Commands

figma-visual-parity doctor                  # preflight: browser, dev server, reference sizes, hide list
figma-visual-parity capture [scenario...]   # export reference PNGs from Figma (needs FIGMA_TOKEN)
figma-visual-parity verify  [scenario...]   # verdict per scenario; --json; exit 1 on mismatch
figma-visual-parity explain <scenario>      # ranked diagnosis

Both verify and explain accept --save-captures, which writes two PNGs per scenario into config.outDir (default figma-visual-parity-output/): the raw capture (<scenario>.capture.png) and a "difference"-blend of capture against reference (<scenario>.diff.png) — matching pixels go black, mismatches light up by how far apart the channels are. Off by default since these are debugging artifacts, not something you want written on every gate run.

Config

figma-visual-parity.config.js in the project root:

export default {
  baseUrl: "http://localhost:5173",
  viewport: { width: 1080, height: 1920 },
  referenceDir: "public/design-overlays",
  figma: { fileKey: "…" },
  hide: ["[class*='_switcher_']", "body>div:not(#root)"],
  overlay: { storageKey: "my-app-overlay" },
  mqtt: { brokerUrl: "mqtt://localhost:1883", baseTopic: "app/main" },
  scenarios: [
    {
      name: "ready",
      node: "4814:189355",
      prepare: [{ click: 'button:text-is("ready")' }],
    },
    { name: "detail", node: "4814:188845", path: "/?step=detail" },
    {
      name: "item-added",
      node: "4814:190002",
      prepare: [
        { mqtt: { topic: "api/doAddItem", payload: { id: "vase-01" } } },
        { waitFor: "[data-testid='item-vase-01']" },
        { key: "Escape" },
      ],
    },
  ],
}

prepare steps are declarative (click, waitFor, waitMs, key, type, mqtt) rather than callbacks on purpose: Playwright cannot serialise a closure into the page, and a stray free variable fails inside the browser where it is easy to miss. The library owns page setup so you never write that code.

  • key presses a single Playwright key or combo ("Enter", "ArrowDown", "Control+A") — useful for use cases driven by hardware buttons or shortcuts rather than clicks.
  • type sends literal text as individual keystrokes.
  • mqtt publishes { topic, payload, retain?, qos? } through config.mqtt, for apps whose state is driven over MQTT rather than the DOM. topic is resolved against mqtt.baseTopic unless it starts with /. The broker connects once per run and is shared across scenarios.

referenceDir defaults to the folder @artcom/react-pixel-overlay already scans, so the same exports serve the human overlay and this tool.

Dev-mode UI

A capture against a dev server must never include the dev server's own chrome, or every downstream number is measuring a difference that isn't a design defect. vite-error-overlay is hidden unconditionally, and its presence is still reported as an environment finding (with the error text) even though it is hidden, since a visible overlay means the app actually errored. Project-specific dev-only UI — a scenario switcher, a debug panel — still needs its own selector in hide; figma-visual-parity doctor reports which selectors are active so you can tell defaults from what you added.

What explain checks, in order

  1. environment — dev-only UI still visible, a Vite error overlay, page errors. Nothing else is trustworthy until this is clean.
  2. font-metrics — an offset that grows with font size. That signature means the app is not loading the files the design composes text from (typically a variable font against static per-weight faces), not that a margin is wrong.
  3. uniform-offset — one global shift. Demoted to a note when the font probe already explains it.
  4. hotspots — blocks where a large share of pixels differ by a moderate amount or more, attributed to the DOM element under them, so you get div.circle.active rather than block (60,180). This catches both a sharp, high-contrast mismatch (wrong text) and a large but lower-contrast one (a whole icon that's missing or extra) — coverage of the block is what's ranked, not the single darkest pixel in it.
  5. baseline-rounding — the residual floor, labelled as expected rather than as a defect.

Verdict

verify fails on any of: mean channel delta over tolerance, share of strongly differing pixels over tolerance, a non-zero global offset, or a single block over tolerance.worstBlockPct. That last one matters — a small control rendered in the wrong state barely moves a frame-wide mean, so a mean-only gate misses it.

Defaults are calibrated against a matching implementation, where per-scenario means land around 0.4–1.1 of 255.

Notes

  • Offset scans are integer-only. Sub-pixel transforms do not move text: browsers snap glyph baselines to whole pixels.
  • References must match the viewport exactly. A mismatch is a hard error, because a wrongly scaled reference produces plausible-looking numbers that mean nothing.
  • The comparison runs inside the page using canvas, so there is no image library in the Node dependency tree.