Skip to content

Article

Machine-Graded Visual Evals for Design Agents

How to turn visual QA from a hopeful glance into fail-closed gates: Playwright captures, calibrated LLM judges, parity scores, and CI checks that stop design-agent output from shipping on vibes.

Last reviewed6 Aug 2026

Reading time6 min read

Section 01

Lab demos pass. Production needs grades.

Design agents are excellent at producing a first screen that looks intentional in a share link. They are much worse at surviving the second week, when a spacing regression, a missing empty state, or a mobile hierarchy break ships because nobody re-checked the same evidence twice.

Human visual QA still matters. It does not scale to every agent iteration on every branch. Machine-graded visual evals close that gap by making visual quality falsifiable: capture the page, compare it to a baseline or brief, score the differences, and fail closed when the score or the checklist says the work is not review-ready.

This article is not about chasing a single magic number. It is about a layered eval: deterministic checks first, calibrated model judgment second, human authority last. Teams that invert that order get expensive theater — an LLM praising a broken checkout because the screenshot looks "clean."

Section 02

The mental model: evidence before opinion

A visual eval has four jobs. Capture must be repeatable. Deterministic checks must catch the cheap failures. A judge — human or model — must interpret the rest against a rubric. A gate must turn the result into a pass or fail that CI and agents both respect.

Fail closed means the default is no. Missing screenshots fail. Unreadable baselines fail. A judge that cannot cite evidence fails. Soft warnings are allowed only for polish bands you explicitly configure. If the pipeline can be talked into green, it is not an eval; it is a mood.

diagramFail-closed visual eval pipeline
1

Design decision

Stable capture — Playwright routes × viewports

2

Design decision

Deterministic layer — DOM asserts, axe, pixel/layout diffs

3

Design decision

Calibrated judge — rubric + evidence citations only

4

Design decision

Score + findings — parity, severity, owner

5

Design decision

CI gate — fail closed on blockers / missing evidence

6

Design decision

Human override — recorded exception with expiry

Deterministic checks run before any LLM judge. Humans can override a fail; agents cannot override a missing evidence packet.

Section 03

What to measure (and what not to)

Useful visual evals measure observables: presence of required regions, order of headings, contrast failures, overflow, clipped CTAs, spacing deltas against a baseline, and parity between desktop and mobile task order when the brief requires it. They do not measure "premium feel" or "modern layout."

Parity scores work when the baseline is intentional. Compare candidate screenshots to an approved reference, or compare implementation to a Figma export that the team has already accepted. Do not compare against the agent's previous attempt and call improvement a grade. Improvement without a fixed target is just motion.

Separate structural scores from aesthetic scores. Structural failures are blockers. Aesthetic disagreements are questions for a designer. Mixing them into one 0–100 "beauty" score teaches the pipeline to average away real bugs.

  • Structural: required landmarks, CTA visibility, form labels, error text present.
  • Layout: overflow, overlap, collapsed spacing, sticky header covering content.
  • Parity: pixel or region diff vs approved baseline within tolerance.
  • Accessibility: axe violations at the configured impact threshold.
  • Out of scope for machine grade alone: brand taste, novel art direction, legal copy.

Section 04

Case study: payment step regression

A design agent refactored a three-step checkout. Desktop looked fine. On a 390px capture, the sticky order summary covered the primary pay button by eight pixels. The PR description said "mobile polish." Without evals, that might have merged after a quick desktop glance.

The team's visual eval job captured /checkout/payment at 1280 and 390, ran axe, and compared region masks for summary, form, and primary CTA. The deterministic layer failed on "primary CTA fully visible." The LLM judge never ran — correctly — because fail-closed rules short-circuit on structural blockers. The agent received the finding, fixed the sticky offset, and the second run passed structural checks before a human reviewed polish findings.

The useful lesson is sequencing. If the judge had run first, it might have praised the denser summary and missed the occlusion. Cheap deterministic checks protect expensive judgment.

screenshotCheckout eval evidence board
Review boardreference · implementation · state

Baseline mobile payment — CTA fully visible

Candidate mobile payment — CTA occluded by sticky summary

Region mask: order summary

Region mask: payment form

Region mask: primary CTA

Gate result: FAIL structural — CTA visibility

Baseline and candidate captures with region masks make occlusion and hierarchy failures obvious to both scripts and reviewers.

Section 05

Playwright capture contract

Unstable captures poison every later layer. Freeze fonts, disable animations, wait for network quiet or a known selector, and store images on disk rather than stuffing base64 into the model context. Name files by route, viewport, and git SHA so CI and local runs remain comparable.

Capture contract snippet (Playwright)
// scripts/visual-capture.ts
import { chromium } from "playwright"

const viewports = [
  { name: "desktop", width: 1280, height: 800 },
  { name: "mobile", width: 390, height: 844 },
]

const routes = ["/checkout/payment"]

for (const route of routes) {
  for (const vp of viewports) {
    const browser = await chromium.launch()
    const page = await browser.newPage({
      viewport: { width: vp.width, height: vp.height },
      reducedMotion: "reduce",
    })
    await page.goto(`http://127.0.0.1:3000${route}`, {
      waitUntil: "networkidle",
    })
    await page.addStyleTag({
      content: "*, *::before, *::after { cursor: none !important; }",
    })
    await page.screenshot({
      path: `evals/captures/${sha}/${routeSlug(route)}-${vp.name}.png`,
      fullPage: true,
    })
    await browser.close()
  }
}

Section 06

Calibrating LLM-as-judge

An uncalibrated judge agrees with whoever sounds confident. Calibration means a fixed rubric, mandatory evidence citations, a small gold set of human-labeled screenshots, and periodic accuracy checks against that gold set. If the judge disagrees with humans on blockers more than your tolerance, it does not belong in CI.

Ask the judge for findings, not vibes. Require severity, evidence (file name + region or quote from accessibility snapshot), user impact, and a smallest fix. Ban redesign proposals during eval. Ban praise. Ban scores without findings.

Use the model after deterministic checks pass or for dimensions those checks cannot see, such as whether the price summary answers the buyer's question. Never let the model override an axe critical or a missing CTA.

Judge rubric prompt
You are a visual eval judge for a checkout payment step.

Inputs:
- brief.md (user job + must-show elements)
- baseline screenshots
- candidate screenshots
- axe.json
- layout-diff.json

Rules:
- Cite evidence for every finding (filename + region or DOM text).
- Do not redesign.
- Do not praise.
- If evidence is missing, return status: FAIL_EVIDENCE.

Severities:
- blocker: task impossible or high-risk mistake
- important: task harder, trust or comprehension harmed
- polish: consistency only

Return JSON:
{
  "status": "pass" | "fail",
  "parityScore": 0-100,
  "findings": [{ "severity", "evidence", "issue", "impact", "fix" }]
}

Fail if any blocker exists or required elements from brief.md are absent.

Section 07

Parity scores without self-deception

A parity score is a compression of diffs, not a substitute for findings. Publish how it is computed: for example, 40% structural checklist, 30% layout diff under tolerance, 20% axe clean at serious+, 10% judge important-count penalty. Make the formula boring and versioned in the repo.

Do not hide a blocker behind a high average. A page can score 91 and still fail CI if the CTA is clipped. Scores communicate trend; gates enforce safety.

tableSeverity and gate matrix
1Missing captures or baselines

CI fail — no human override by agent

2Any blocker finding

CI fail — human may file timed exception

3axe serious/critical

CI fail

4parityScore < 80 with important findings

CI fail

5parityScore >= 80, polish only

CI pass — optional warn annotation

6Judge disagrees with gold set > threshold

Disable judge lane until recalibrated

CI should key off severity and evidence completeness, not a single vanity score.

Section 08

Good vs bad eval setups

The difference between a real gate and decorative CI is visible in what can go green.

tableGood vs bad visual eval setups
1Bad: judge-only screenshot opinions

Good: deterministic checks before judge

2Bad: compare to last agent attempt

Good: compare to approved baseline or brief

3Bad: single desktop capture

Good: fixed viewports + named routes

4Bad: score without findings

Good: findings required; score derived

5Bad: warn-only forever

Good: fail closed on blockers and missing evidence

If agents can skip evidence and still pass, you built reporting, not evaluation.

Section 09

Wiring the gate into CI and agents

Agents should run the same command humans run. Put capture + check + judge behind npm run eval:visual and call it from AGENTS.md as a required pre-review step. In CI, fail the job on non-zero exit. Store captures as artifacts so reviewers can see what the machine saw.

For local agent loops, keep the judge optional until structural checks pass. That saves tokens and prevents the model from negotiating with itself about whether a clipped button is "still mostly visible."

package.json scripts + CI sketch
{
  "scripts": {
    "eval:capture": "tsx scripts/visual-capture.ts",
    "eval:check": "tsx scripts/visual-check.ts",
    "eval:judge": "tsx scripts/visual-judge.ts",
    "eval:visual": "npm run eval:capture && npm run eval:check && npm run eval:judge"
  }
}

# GitHub Actions (sketch)
- run: npm run build && npm run start &
- run: npm run eval:visual
- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: visual-evals
    path: evals/

Section 10

Risks, limits, and honesty bars

Pixel diffs flake when fonts, anti-aliasing, or lazy media shift. Prefer region masks and structural asserts for blockers; use tight pixel tolerances only on stable chrome. LLM judges drift as models change — pin versions where you can and re-run the gold set after upgrades.

Evals do not replace accessibility with assistive technology, brand critique, or legal review. They catch repeatable visual and structural failures early. Say that out loud in your team docs so nobody treats a green badge as a ship certificate.

Cost is real. Full-page screenshots into a judge are expensive. Disk files, accessibility snapshots, and shortlist-only image crops keep the loop affordable. FinOps for critique fleets belongs beside this practice, not as an afterthought.

Section 11

Reusable visual eval workflow

Use this whenever an agent changes UI that users will see. Same steps locally and in CI.

Visual eval workflow
1. Freeze intent
   - brief.md with user job + must-show elements
   - approved baseline captures (or explicit first-baseline mode)

2. Capture
   - named routes × viewports
   - reduced motion, settled network, disk output

3. Deterministic checks
   - required selectors / CTA visibility
   - axe threshold
   - layout or region diff

4. Judge (only if step 3 did not fail closed)
   - rubric JSON findings with evidence
   - derive parityScore from published formula

5. Gate
   - fail on blockers, missing evidence, or score policy
   - upload artifacts

6. Fix + recapture
   - one scoped pass
   - never "fix" by loosening the gate silently

Sources

Sources & further reading

Related articles

Keep reading on Visual evals.

35

Multi-CLI Literacy for Designers

Design teams will not standardize on one coding agent. This field note covers the shared literacy across Claude Code, Cursor, Codex, and Gemini-class CLIs: instruction files, skills, MCP, permissions, and verification habits that travel when the binary changes.

Reviewed
30 Jul 2026
Reading time
3 min
Read article
34

Agent FinOps for Design Orgs

A field note on keeping design-agent spend under control: where critique fleets burn tokens, how to budget capture and judge lanes, and a lightweight cost review ritual that does not require a finance degree.

Reviewed
28 Jul 2026
Reading time
3 min
Read article
33

Authorship, Disclosure, and Studio Governance for Agentic Design

A practical governance kit for design studios and campus programs: when to disclose agent assistance, how to keep critique logs that assign accountability, and why verification rituals matter more than generation speed when work carries a human signature.

Reviewed
8 Aug 2026
Reading time
5 min
Read article
Newsletter

Get the next visual-eval rubric and CI gate template by email.

The newsletter is the update channel for article revisions, tool changes, and field-tested workflows.

Processed by Buttondown. You can unsubscribe from any email.

Further reading

For deeper reading, explore the books behind the Agentic Design School curriculum.

The Agentic Designer cover
Curriculum

The Agentic Designer

How AI agents are transforming product design.

The operating model for product designers, design leads, and builders who need to understand what changes when agents join design work.

Claude Code for Designers cover
Curriculum

Claude Code for Designers

A designer's guide to AI-assisted workflows.

A practical guide for designers who want to work directly with coding agents without turning it into a programming manual.

Open Design cover
Curriculum

Open Design

Local-First, Agent-Native Design-as-Code — The Open-Source AI Design Alternative

A practical field guide to running local-first, agent-native design-as-code workflows without surrendering brand quality or vendor independence.