Percy vs Chromatic for component visual testing
This page belongs to the Visual Testing Tool Comparisons section of the Visual Regression & Snapshot Strategies guide. Once you have decided you want a hosted visual testing service driven by Storybook — rather than self-hosted Playwright screenshots — the choice narrows to two mature products: Percy (BrowserStack) and Chromatic. This page compares them on the five axes that actually decide the outcome: Storybook integration depth, responsive-width capture, review workflow, baseline branching, and pricing model.
Problem statement
Percy and Chromatic look interchangeable in a feature-matrix skim. Both snapshot your Storybook in the cloud, both render a diff, both gate a pull request. Teams pick one on a coin flip, wire it into CI, and only discover the differences that matter three months in — when the monthly bill scales differently than expected, when responsive breakpoints double the snapshot count, or when a long-lived feature branch drowns reviewers in diffs against unrelated main changes.
The concrete pain: a design-system team adopts one tool, defines six responsive widths because their components are fluid, and finds their snapshot consumption is six times what they modeled — because the two tools bill responsive widths on completely different meters. The tool was fine; the mental model of how it charges was wrong.
Root cause
Percy and Chromatic differ most in what a snapshot is and what a baseline is tied to — and those two decisions ripple into responsive capture, review, and pricing.
Percy takes one DOM capture per story and re-renders that single capture at each configured width inside its cloud, then diffs each rendering. Chromatic captures a discrete snapshot per story per viewport per browser on its hosted browsers. That difference is why the same six-width component costs differently on each. On baselines, Chromatic — built by the Storybook team — binds each accepted baseline to a git branch, while Percy compares against a single configured base branch. Understanding those two roots explains every downstream trade, including the threshold and tolerance knobs each exposes for suppressing diff noise.
Minimal reproduction
The same fluid PriceCard story, captured at three widths, meters differently on each service:
// PriceCard.stories.jsx — one story, three responsive widths wanted
import { PriceCard } from '../src/PriceCard';
export default { component: PriceCard, title: 'PriceCard' };
export const Standard = {
args: { plan: 'Team', price: 29 },
parameters: {
// Percy re-renders one capture at each width — 1 screenshot
percy: { widths: [375, 768, 1280] },
// Chromatic captures one snapshot per viewport — 3 snapshots
chromatic: { viewports: [375, 768, 1280] },
},
};
One story, three widths: Percy counts this as one screenshot with three renders; Chromatic counts it as three snapshots. Multiply across a 40-story library and the difference is what surprises the invoice.
Step-by-step fix
1. Configure Percy against Storybook
npm install --save-dev @percy/cli @percy/storybook
# .percy.yml
version: 2
snapshot:
widths: [375, 768, 1280] # applied to every story unless overridden
min-height: 1024
percy-css: |
.animated-spinner { animation: none !important; } # freeze motion for stable diffs
storybook:
args:
docs: false
# build Storybook, then snapshot every story through Percy
npm run build-storybook
npx percy storybook ./storybook-static --project-token=percy_tok_1a2b3c
What this does: percy storybook enumerates each story, uploads one DOM capture per story to Percy’s cloud, and re-renders it at each width in widths. The percy-css block neutralizes animation so a spinner mid-frame does not cause a false diff.
2. Configure Chromatic against Storybook
npm install --save-dev chromatic
// .storybook/preview.js — project-wide Chromatic defaults
export const parameters = {
chromatic: {
viewports: [375, 768, 1280], // one snapshot per viewport
pauseAnimationAtEnd: true, // settle animations before capture
diffThreshold: 0.063, // tolerance, see the Chromatic threshold guide
},
};
# build + upload Storybook; Chromatic renders each story on hosted browsers
npx chromatic --project-token=chpt_a1b2c3d4e5f6 --only-changed
What this does: Chromatic builds your Storybook, uploads it, and captures a discrete snapshot per story per viewport. --only-changed (TurboSnap) uses the git diff and webpack dependency graph to skip stories unaffected by the commit, which is Chromatic’s primary lever for controlling snapshot volume.
3. Compare on the five deciding axes
| Axis | Percy (BrowserStack) | Chromatic |
|---|---|---|
| Storybook integration | @percy/storybook plugin enumerates stories; per-story percy param |
Built by Storybook team; story params are snapshot config |
| Responsive-width capture | One capture re-rendered per widths entry |
Discrete snapshot per chromatic.viewports entry |
| Cross-browser | Chrome, Firefox, Safari, Edge (plan-dependent) | Chrome, Firefox, Safari, Edge; each browser a snapshot |
| Review workflow | Snapshot-approval board, group by diff | Per-story accept/deny UI, comments, assignments |
| Baseline model | Diff against configured base branch | Baseline bound to git branch, carried on merge |
| Change-skip optimization | Responsive re-render (fewer captures) | TurboSnap skips unchanged stories via git graph |
| Pricing meter | Per screenshot (widths re-rendered free-ish) | Per snapshot (each viewport/browser counts) |
| Best-fit signal | Many responsive widths, one main branch | Deep Storybook use, many branches, per-story review |
4. Recommend by scenario
- Fluid components needing many responsive widths, team works off one
main→ Percy. The re-render-per-width model keeps cost flat as you add breakpoints, and single-base-branch baselines match your workflow. - A design system with heavy Storybook use and many concurrent feature branches → Chromatic. Git-branch baselines stop cross-branch diff noise, TurboSnap controls volume, and the per-story review UI fits designer sign-off. Gate it as a required visual review check.
- You already pay for BrowserStack → Percy folds into that account and its real-device cross-browser grid, avoiding a second vendor.
- You need per-viewport browser fidelity over cost → Chromatic, because each width is a true independent capture rather than a re-render of one DOM.
Verification
Run both against the same commit and read the reported snapshot counts — that is the number your bill is built on.
# Percy — reports screenshots, widths folded in
npx percy storybook ./storybook-static
# [percy] Snapshot taken: 'PriceCard: Standard'
# [percy] Finalized build #128: 40 snapshots, 3 widths each
# [percy] https://percy.io/org/design-system/builds/128
# Chromatic — reports snapshots per viewport
npx chromatic --project-token=chpt_a1b2c3d4e5f6
# ✓ Started build 128
# ✓ Published 40 stories → 120 snapshots (3 viewports)
# → 2 changes must be reviewed
# https://chromatic.com/build?appId=...&number=128
The same 40 stories at three widths read as “40 snapshots, 3 widths each” on Percy and “120 snapshots” on Chromatic. Both are correct; they are just different meters. Model your cost on the meter the tool actually bills, not on story count.
Edge cases and caveats
- TurboSnap can under-capture on config changes. Chromatic’s
--only-changedrelies on the webpack dependency graph; a change to a global theme file or a.storybook/preview.jsdecorator may not be traced to every dependent story, so periodically run a full build to reset baselines you might have skipped. - Percy’s single-DOM-capture misses per-width JavaScript. Because Percy re-renders one captured DOM at each width, a component that runs a
ResizeObserverto change its markup at a breakpoint will not reflect that JS-driven change at every width — the DOM was captured once. Components that restructure via CSS media queries are fine; those that restructure via JS need per-width capture. - Cross-browser multiplies both meters. Adding Firefox and Safari doubles or triples snapshot consumption on either tool. Reconcile the browser set here with your cross-browser matrix so you are not paying to re-capture browsers that never regress.
FAQ
Which has deeper Storybook integration, Percy or Chromatic?
Chromatic is built by the Storybook maintainers and treats each story as a first-class snapshot, reading story parameters such as chromatic.viewports and chromatic.disableSnapshot directly. Percy integrates through the @percy/storybook plugin, which enumerates stories and snapshots them, with per-story options set via a percy parameter. Both cover Storybook well; Chromatic’s coupling is tighter because story configuration and snapshot configuration are the same object.
How do Percy and Chromatic handle responsive-width capture?
Percy captures one DOM snapshot and re-renders it in its cloud at each width listed in the widths array, so extra widths are cheap re-renders of one capture. Chromatic captures a separate snapshot per viewport listed in chromatic.viewports, so each width is billed as its own snapshot. If you need many widths, Percy’s re-render model is usually more economical; if you need per-viewport browser accuracy, Chromatic’s discrete snapshots are more faithful.
How does baseline branching differ between Percy and Chromatic?
Chromatic ties baselines to git branches: each branch keeps its own accepted baseline and merging carries the approval to the target branch, so a feature branch never diffs against unrelated work. Percy compares against a configured base branch and auto-approves baselines on that branch after merge. Chromatic’s per-branch model reduces cross-branch noise; Percy’s is simpler to reason about when everyone targets one main branch.
Related
- Visual Testing Tool Comparisons — the parent section comparing the visual testing tools this page evaluates.
- Jest Snapshots vs Playwright Visual Diff vs Chromatic — a wider comparison that places Chromatic against self-hosted and DOM-level options.
- Configuring Chromatic Threshold Settings for Pixel-Perfect Diffs — tune Chromatic’s diff sensitivity once you have chosen it.
- Updating Visual Baselines Without Approving Regressions — accept intended diffs from either service without baking in real regressions.
- Making Visual Review a Required Status Check — enforce sign-off on Percy or Chromatic diffs before merge.