Skip to content

Storybook GitHub Action

The pxdiff/storybook GitHub Action uploads a Storybook build, discovers stories, captures screenshots, and diffs them against baselines — all in one step.

.github/workflows/visual.yml
name: Visual Regression
on: pull_request
jobs:
visual:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Storybook
run: npm run build-storybook
- name: pxdiff Visual Regression
uses: pxdiff/storybook@v1
with:
api-key: ${{ secrets.PXDIFF_API_KEY }}
build-dir: ./storybook-static
InputRequiredDefaultDescription
api-keyyespxdiff API key
build-diryesPath to the Storybook build directory
suiteno"default"Suite name for grouping snapshots
auto-baselineno"false"Set baselines from captured screenshots (no diff)
auto-approveno"false"Run diff and automatically approve all changes and set baselines
thresholdnoDiff threshold 0.01.0
filternoGlob pattern to filter stories
only-changedno"false"Only capture stories affected by code changes (change detection)
api-urlno"https://pxdiff.com"pxdiff API URL
cli-versionno(set at publish)@pxdiff/cli version range to install
OutputDescription
diff-urlURL to the diff review page
status"passed" or "failed"
changedNumber of changed snapshots
newNumber of new snapshots
capture-idCapture ID
diff-idDiff ID
- name: pxdiff
uses: pxdiff/storybook@v1
with:
api-key: ${{ secrets.PXDIFF_API_KEY }}
build-dir: ./storybook-static
- name: pxdiff
uses: pxdiff/storybook@v1
with:
api-key: ${{ secrets.PXDIFF_API_KEY }}
build-dir: ./storybook-static
suite: design-system
filter: "Button*"
threshold: "0.1"

Only capture stories affected by code changes. Requires building Storybook with --stats-json:

- name: Build Storybook
run: npm run build-storybook -- --stats-json
- name: pxdiff
uses: pxdiff/storybook@v1
with:
api-key: ${{ secrets.PXDIFF_API_KEY }}
build-dir: ./storybook-static
only-changed: true

See the Storybook guide for details on how change detection works.

Use auto-baseline to set baselines from captured screenshots without diffing. Useful for initial setup or on merge to the default branch:

- name: pxdiff
uses: pxdiff/storybook@v1
with:
api-key: ${{ secrets.PXDIFF_API_KEY }}
build-dir: ./storybook-static
auto-baseline: "true"

Use auto-approve to run the diff and automatically approve all changes, setting new baselines. Unlike auto-baseline, this still creates a diff so you can see what changed:

- name: pxdiff
uses: pxdiff/storybook@v1
with:
api-key: ${{ secrets.PXDIFF_API_KEY }}
build-dir: ./storybook-static
auto-approve: "true"
- name: pxdiff
id: pxdiff
uses: pxdiff/storybook@v1
with:
api-key: ${{ secrets.PXDIFF_API_KEY }}
build-dir: ./storybook-static
- name: Comment on PR
if: steps.pxdiff.outputs.changed != '0' || steps.pxdiff.outputs.new != '0'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `**pxdiff:** ${steps.pxdiff.outputs.changed} changed, ${steps.pxdiff.outputs.new} new snapshots.\n\n[Review diff](${steps.pxdiff.outputs.diff-url})`
})

A complete workflow that builds Storybook, runs visual regression, and requires approval:

name: Visual Regression
on: pull_request
jobs:
visual:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run build-storybook
- name: pxdiff Visual Regression
id: pxdiff
uses: pxdiff/storybook@v1
with:
api-key: ${{ secrets.PXDIFF_API_KEY }}
build-dir: ./storybook-static
suite: storybook

The action runs these steps:

  1. Install CLI — installs @pxdiff/cli globally (skipped if already available)
  2. Upload & Capture — runs pxdiff storybook <source> which uploads the Storybook build, discovers stories, and captures screenshots
  3. Diff — runs pxdiff diff to compare against baselines (skipped when auto-baseline is true; runs with --auto-approve when auto-approve is true)
  4. Report — parses JSON output and sets GitHub Actions outputs

The action automatically detects the branch and commit from the GitHub Actions environment. No additional git configuration is needed.

pxdiff reads Storybook parameters to control capture behavior. Set these in your stories:

Button.stories.ts
export default {
title: "Components/Button",
parameters: {
pxdiff: {
delay: 500, // Wait 500ms before capture
modes: { // Capture multiple modes
mobile: { viewport: { width: 375, height: 812 } },
desktop: { viewport: { width: 1280, height: 720 } },
},
selector: ".button-wrapper", // Capture specific element
cropToViewport: true, // Crop to viewport bounds
ignoreSelectors: [".timestamp"], // Hide dynamic elements
diffThreshold: 0.1, // Per-story threshold
disable: false, // Set true to skip this story
},
},
};

If you’re using the viewports parameter instead of modes, pxdiff generates one target per viewport width:

parameters: {
pxdiff: {
viewports: [375, 768, 1280], // One capture per width
},
}

modes is preferred over viewports as it supports arbitrary configuration beyond just width.