Component visual testing, explained
Component visual testing renders a component in isolation with fixed props, screenshots it, and diffs against a baseline. Storybook, Vitest, or Playwright.
Component visual testing renders a single component in isolation, in a known state, screenshots it, and diffs that image against a saved baseline, so a change in the rendered pixels shows up as a highlighted diff. Instead of driving a whole user flow and asserting the page looks right, you feed one component fixed inputs and compare the result to the last time it was correct. A Button is the canonical example: render it at every size, in every variant, across its states and both themes, capture that grid once, and any restyle that moves it fails the check while your unit tests stay green.
What component visual testing is
A component visual test has three parts: render the component with fixed props, take a screenshot, compare it to a baseline. The first run records a baseline image for each component state; every run after that diffs against it and flags what moved for a human, or a coding agent, to judge. If a token flip breaks contrast, a refactor nudges the padding, or a dependency bump restyles a card, the diff catches it. It is the same mechanism as any visual regression check, aimed one level below the page, and the broader idea is covered in screenshot testing, explained.
Why isolating a component removes most flake
The reason to test a component in isolation rather than as part of a live page is that isolation removes almost everything that makes a screenshot flake. There is no page scroll position to drift, no third-party script injecting a cookie banner or a chat widget, no real API returning a different payload this second. You supply the props, so the render is a pure function of inputs you control, and the same component renders the same pixels every run. On a live page a dynamic list or an unpredictable search result is a genuine problem; in an isolated component it dissolves, because you are the one supplying the data.
One global input still escapes that isolation: the clock. A component that reads Date.now() or new Date() renders a different timestamp on every capture, and that is the number-one source of flaky component diffs. Freezing time is a one-line fix, covered in depth for the story path in kill flaky Storybook diffs. Handle the clock and a well-isolated component is deterministic.
The Button matrix shows why the isolation is worth it. A button has a handful of sizes, a few variants, a set of interactive states, and a light and a dark theme, and every combination has to look right. Covering that on a live page means navigating to a screen that happens to use each one; covering it in isolation means one grid story that renders the whole matrix in a single shot. Take an illustrative 5 sizes times 3 variants and you already have fifteen cells that a single restyle can break, each one captured deterministically.
import type { StoryObj } from "@storybook/react";
import { Button } from "./Button";
const sizes = ["xs", "sm", "md", "lg", "xl"] as const;
const variants = ["primary", "secondary", "ghost"] as const;
// One story captures the whole matrix: every size x variant in a single shot.
// Light and dark are captured as separate modes on top of this grid.
export const Matrix: StoryObj = {
render: () => (
<div style={{ display: "grid", gridTemplateColumns: `repeat(${sizes.length}, auto)`, gap: 12 }}>
{variants.map((variant) =>
sizes.map((size) => (
<Button key={`${variant}-${size}`} variant={variant} size={size}>
{variant}
</Button>
)),
)}
</div>
),
};Three ways to capture a component
You do not need a new kind of test to do this. Component visual testing rides on whatever you already use to render a component, and there are three common paths. Each one is a full post of its own; the short version is which artifact it captures.
- Storybook stories. If you already write stories, each story is a component in a known state, so it is a capture target for free. This is the most deterministic path because a story mocks its own inputs. See Storybook visual regression testing and the Storybook quickstart.
- Vitest browser-mode tests. If your components are exercised in Vitest browser mode, the point in the test where the component is mounted and settled is a capture target, so your existing render-and-assert tests double as visual tests. See Vitest visual testing and the Vitest quickstart.
- Playwright component or page tests. If you drive components or whole pages with Playwright, you capture at the point the page has settled. This is the path for real-page and archive-replay captures. See Playwright visual testing and the Playwright quickstart.
When each path fits
Pick the path that matches where your components already render, not a new one. Have stories? Use them - they are the cleanest inputs and the least flake. Have a browser-mode Vitest suite but no Storybook? Capture there and skip maintaining a second harness. Testing real pages or flows you cannot mock into a story, or need production content? Use Playwright. Most teams end up mixing them: stories for the component library, Playwright for the handful of full-page flows that only make sense end to end.
How component visual tests fit a testing strategy
Component visual tests are the wide base of a sane test strategy: a lot of them isolating each diff to the component that moved, under a thin layer of end-to-end tests for the flows that genuinely need a full walkthrough. That is the shape that holds up when a coding agent is opening pull requests all day. The full case for it, and why a top-heavy e2e suite breaks first, is invert the testing pyramid; a high-leverage companion move is one Storybook story per page, which captures a whole screen in a single story instead of dozens of near-duplicates.
Component visual testing is also the layer that makes visual testing for agents work. An agent ships UI it cannot see, so a screenshot of the isolated component, diffed against its baseline and judged by an AI that tells an intended change from a regression, is the feedback loop that catches what the agent got wrong before you do. Start with the path that matches your codebase, or let the setup skill wire the capture and CI for you.
Component visual tests on every pull request
UI Verify screenshots your components in isolation on every pull request, whether they come from Storybook, Vitest, or Playwright, and an AI judge tells an intended change from a real regression - so you review decisions, not diffs.
Start for freeNo credit card required.
Set up visual testing
From no visual tests to a green check on every pull request.