VA

Automates UI smoke-testing for Color Lab to prove functionality through browser actions.

Install

mkdir -p .claude/skills/validate-ui && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/19011" && unzip -o skill.zip -d .claude/skills/validate-ui && rm skill.zip

Installs to .claude/skills/validate-ui

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Drive Color Lab's real UI in a browser and verify a feature area actually works — routes, controls, selectors, and the exact observable that proves each step. Use this whenever the user asks to validate, check, verify, exercise, smoke-test, or "click through" any part of the app, and whenever they name a feature area to validate ("validate scale", "validate groups", "check the preview still works", "does export still work?", "make sure saving a palette works"). Also use before shipping a UI change, when reproducing a reported UI bug, or any time you are about to explore the app in a browser to find out how it works — that exploration is what this skill exists to replace. Do NOT use for `pnpm validate` (typecheck + lint + tests) — that is a different thing entirely.
775 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Intermediate

Key capabilities

  • Drive Color Lab UI in a browser
  • Verify feature areas like routes and controls
  • Perform assertions after each action
  • Reproduce UI bugs
  • Validate UI changes before shipping

How it works

The skill drives the Color Lab UI in a browser, executing actions, observing changes, and asserting expected outcomes for specific feature areas.

Inputs & outputs

You give it
A feature area to validate (e.g., 'validate scale', 'validate groups')
You get back
A report detailing what was driven, observed, and any broken aspects of the UI

When to use validate-ui

  • Smoke-test UI changes
  • Verify feature functionality
  • Reproduce UI bugs

About this skill

Validate the Color Lab UI

Drive the real app as a user and prove a feature works. Assert between actions — never chain two on an assumption; the loop below is why.

Read this file, then read exactly one references/<area>.md for the area you're validating. Don't load them all.

The loop

1. act      — one command
2. observe  — snapshot (or get the specific observable)
3. assert   — the thing you expected to change actually changed
4. repeat

You cannot see the page. You are working from a mental model, and the model is wrong the moment you act. So you re-read after every action — not when something looks broken, because it will not look broken.

✓ Done is not a result

agent-browser prints ✓ Done when the command ran. It says nothing about whether it did anything. Every trap in this file is an instance of that:

CommandPrintsActually
fill on a slider✓ Donevalue jumped to the range's midpoint
type / keyboard type / select on a slider✓ Donenothing at all
focus on a control in a collapsed panel✓ Donefocus went nowhere (inert) — every later press is swallowed
clicking a [disabled] button✓ Donenothing at all — and the unchanged state reads as a real result
find role button click --name "Save"✓ Donehit Save palette, not the modal's Save
clicking a toggle whose state was persisted open✓ Doneyou just closed it

None of these error. All of them produce a green, confident, wrong result. The only defence is to look.

So: never chain two actions on an assumption. After a click that opens something, assert it opened. After a click that toggles something, assert which way it went. Re-snapshot before reusing any @ref — refs die on every re-render.

A covered control is not one of these traps: any click on one errors and names the covering element (✗ covered by <header.fixed.top-0>). That's a signal, not a failure — read it and clear the coverer.

Prefer click @ref over find role … click anyway: find matches by substring, so a name that looks unique often isn't — --name "Save" hits Save palette, and a bare Select matches Select <Name>, Select variant and Select lock at once. A @ref names exactly one.

What the app is

An OKLCH color-scale generator. You pick base colors; it generates a tonal scale (3–20 steps) for each. Everything else — curves, groups, presets, export, contrast tools — feeds or renders that.

The palette IS the URL. Every structural edit rewrites the address bar; back/forward is undo/redo. This is why most assertions are URL assertions: if the URL didn't change, the edit didn't happen.

Routes: / redirects to /p. /p/<slug> is the entire generator. /palettes is the saved list (auth-gated). /about, /privacy, /terms, /oklch-vs-hsl, /custom-color-scales are static.

Boot

The dev server is https://color-lab.localhost (portless proxy) — not localhost:3000.

pnpm dev   # if it isn't already up

agent-browser open https://color-lab.localhost/p/Primary-73.0_0.23001_321
agent-browser set viewport 1440 900
agent-browser wait --load networkidle

Two seed palettes, from e2e/__setup__/constants.ts:

  • single/p/Primary-73.0_0.23001_321
  • palette/p/Primary-60_0.21_150/Secondary-60_0.139_227/Tertiary-60_0.266_304

Each area file names the seed it wants.

The single seed rewrites itself on load. That is expected. Plain precision rounding, in P3 and sRGB alike — formatOklchUrl (src/utils/color.ts) rounds L to 2 decimals and C to 3:

/p/Primary-73.0_0.23001_321   →   /p/Primary-73_0.23_321
        ^^^^ trailing zero          ^^^^^^^ 0.23001 → 0.23

Assert against Primary-73_0.23_321. The palette seed is canonical and stays put. Gamut has nothing to do with it — e2e/basic.spec.ts:56 asserts the rewritten form while forcing P3.

Step zero: read the opening state, don't assume it

appStore persists to localStorage (key color-lab). A pre-paint script in app/layout.tsx stamps some of it onto <html> before React mounts. The starting state is restorable, not fixed.

Persisted: gamut, showSidebar, showPreview, showColorOptionsPanel, showPaletteOptionsPanel, view, colorSpacing, exportFormatType, exportColorFormat.

view, colorSpacing and the two export settings are persisted but are not on <html>. A reload restores whatever you last left — not List view, not Wide spacing, not the default export tabs.

A toggle you click may already be on, so your click turns it off. Read the state first; if it isn't what the area file expects, set it explicitly or clear storage.

The three observables

Don't eyeball screenshots. Screenshots are for showing the human.

1. The URL — the palette state. Colors are path segments (/p/Name-L_C_H); options are short query keys. Per-color overrides ride on the segment after a -, comma-separated (Primary-60_0.21_150-f:1.2,k:400).

KeyMeansKeyMeans
isteps (3–20)s / osaturation / apply-to-all
mmode l/d/rklocked step
vvariantflightness curve
cchroma curvehhue shift
n / xmin / max lightnessggroup: b/n/s/d

Defaults are dropped from the URL entirely — so "reset" is asserted as the param disappearing, not as it returning to a value.

2. <html> — the app's chrome state. Global layout flags with no accessible surface, so there is nothing in the tree to read instead:

agent-browser eval "JSON.stringify(document.documentElement.dataset)"
# {"gamut":"srgb","p3Supported":"false","sidebar":"open","preview":"open",
#  "colorOptions":"closed","paletteOptions":"closed"}

data-gamut (p3|srgb) · data-sidebar · data-preview · data-color-options · data-palette-options (each open|closed) · data-p3-supported. dataset camelCases themdataset.colorOptions, not dataset['data-color-options']. Dark mode is class="dark", not a data attribute.

Three different surfaces say "color options" — keep them apart. data-color-options tracks only the global Advanced Options panel (showColorOptionsPanel). The per-color Change color options Collapse has no chrome flag — it's proven open by its inputs being mounted. Steps/variant/lock live under data-palette-options.

Headless Chrome reports p3Supported: "false", so the app opens in gamut: srgb — the environment, not a bug. It does mean you validate the sRGB path by default, which the e2e suite never exercises (it forces P3 on).

3. ARIA — element state. aria-current on the active ColorItem · aria-pressed on group chips · aria-selected on tabs · aria-checked on radios · aria-expanded on Toggle Bottom Bar.

agent-browser snapshot -i              # your default read
agent-browser get count '[data-testid=Scale]'
agent-browser get attr @e5 aria-pressed
agent-browser get url

The eval rule

Read state through the accessibility tree, not the DOM. eval has exactly three legitimate uses:

  1. Reading <html>'s data-* chrome state (observable 2 — it has no ARIA surface by design).
  2. Patching navigator.clipboard (clipboard reads are blocked).
  3. setRange — setting a channel slider's value, because agent-browser genuinely cannot (see Sliders).

Everything else — element counts, aria-*, text, tab selection, panel contents — comes from snapshot / get.

Reaching for eval to prove a control's state — className, getComputedStyle, getBoundingClientRect, a bare data-* — means stop. That control has no accessible state, and that is an accessibility bug. Report it. Class-sniffing hides the defect and trains the next agent to hide it too.

The distinction that matters: eval is banned as a substitute for looking. It is correct as an implementation of an action agent-browser can't perform. setRange is the second; it is not a licence for the first.

eval reuses one JS context, so a bare const x = … throws Identifier 'x' has already been declared on the second call. Wrap every body in an IIFE.

Sliders

Every slider is an <input type="range">, but they come from two libraries that commit on different events. That, and only that, is why they differ.

SliderWhereCommits onDrive with
ChannelLightness, Chroma, Huesidebar ColorIteminputsetRange
Option — steps, saturation, every curvePalette Options, Advanced Options, per-color overridesonChangeEndfocus + press

Both land exactly, in one step. Never fill either.

Option sliders → focus + press

HeroUI/react-aria. They commit on onChangeEnd, which only real input fires — a synthetic value change does nothing, so setRange will not move the URL here. e2e drives them this way and explains why at e2e/scale.spec.ts:44-48.

agent-browser focus 'input[name="lightnessCurve"]'
agent-browser press PageDown          # 1.3 → 1.2, URL gains ?f=1.2

press takes a key, not a selector — it acts on whatever is focused, so every keyboard step is two commands. Focus persists across presses. One press = one tick: PageUp/PageDown = 0.1 on the curve sliders, 10 on hueShift; arrows = 1 on steps; Home/End = min/max.

Assert the panel is open before you focus. A collapsed panel's content is inert, and an inert input cannot take focus — focus still prints ✓ Done, and every press is silently swallowed. How you assert open depends on the panel: Advanced Optionsdata-color-options (it flips in lockstep with inert); Palette Optionsdata-palette-options; a per-color panel → its inputs being present (it unmounts them when closed — see


Content truncated.

When not to use it

  • When performing `pnpm validate` for typecheck, lint, and tests
  • When the user is not asking to validate, check, verify, exercise, smoke-test, or click through the app
  • When the user is not naming a specific feature area to validate

Limitations

  • Cannot see the page, relies on a mental model.
  • Assertions are required after every action to confirm changes.
  • Some UI actions may report success without actual effect if not observed.

How it compares

This skill provides a structured, observable method for UI validation, preventing assumptions and ensuring actual changes are verified, unlike manual clicking without explicit assertions.

Compared to similar skills

validate-ui side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
validate-ui (this skill)012dReviewIntermediate
webapp-testing3533moReviewIntermediate
ui-ux-expert-skill919moReviewAdvanced
accessibility425moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

webapp-testing

anthropics

Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.

353585

ui-ux-expert-skill

fercracix33

Technical workflow for implementing accessible React user interfaces with shadcn/ui, Tailwind CSS, and TanStack Query. Includes 6-phase process with mandatory Style Guide compliance, Context7 best practices consultation, Chrome DevTools validation, and WCAG 2.1 AA accessibility standards. Use after Test Agent, Implementer, and Supabase agents complete their work.

91244

accessibility

tech-leads-club

Audit and improve web accessibility following WCAG 2.1 guidelines. Use when asked to "improve accessibility", "a11y audit", "WCAG compliance", "screen reader support", "keyboard navigation", or "make accessible".

42174

playwright-browser-automation

lackeyjb

Complete browser automation with Playwright. Auto-detects dev servers, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, automate any browser task. Use when user wants to test websites, automate browser interactions, validate web functionality, or perform any browser-based testing.

29146

svelte-expert

Raudbjorn

Expert Svelte/SvelteKit development assistant for building components, utilities, and applications. Use when creating Svelte components, SvelteKit applications, implementing reactive patterns, handling state management, working with stores, transitions, animations, or any Svelte/SvelteKit development task. Includes comprehensive documentation access, code validation with svelte-autofixer, and playground link generation.

11107

browser-daemon

noiv

Persistent browser automation via Playwright daemon. Keep a browser window open and send it commands (navigate, execute JS, inspect console). Perfect for interactive debugging, development, and testing web applications. Use when you need to interact with a browser repeatedly without opening/closing it.

587

Search skills

Search the agent skills registry