If you build React UI with a coding agent, you've probably seen this: every page looks fine on its own, but put them side by side and something's off. The Team page has a flat grey button with a 2px black border. Reports has a pill. Settings has a red one with radius 4. Billing uses #4263eb, which is almost your primary color, but not quite.
Nobody decided this. The agent just wrote a new button inline each time, because it didn't know (or forgot) that src/components/Button.tsx exists.
I built Tavero to catch exactly that drift and hand it back to the agent as concrete fixes. It's free and open source.
What it does
Tavero scans your running React app — what the browser actually renders — at mobile, tablet and desktop widths. It runs four rules, and every finding names the component and the source line:
- Buttons — distinct button styles, near duplicates, and buttons written in page markup instead of the shared component.
- Cards — card-like containers that differ in background, border, radius, padding or shadow.
- Colors — colors outside your tokens, the nearest token, and where the literal is written.
- Scales — radius, spacing, font size and shadow values that aren't on the scale your tokens declare.
Here's what a button finding looks like on the Acme demo (6 pages, 3 widths), which turned up 7 text button styles:
Button "primary" · canonical · 9 renders
src/components/Button.tsx:4 bg #3b5bdb, radius 6, padding 10/16, 14px/600
written in Team · 3 renders
src/pages/Team.tsx:6 bg #f0f0f0, radius 0, padding 1/6, 13px/400, border 2px #000
written in Settings · 3 renders
src/pages/Settings.tsx:12 bg #e03131, radius 4, padding 10/16, 14px/600
written in Billing · 3 renders
src/pages/Billing.tsx:8 bg #4263eb, radius 8, padding 11/18, 14px/600
And a scale finding:
radius off the scale: 4px, 8px, 14px, 999px · declared: 10px, 6px
The agent closes the findings
Tavero runs as an MCP server, so the agent can use it directly. The loop is:
- Read the context — canonical components, tokens and conventions, in about 1,500 tokens.
- Open a finding — file and line, the current value, and the token or component to use.
- Fix and rescan — only the routes and the rule it touched. Seconds, not minutes.
- Check the diff — done when the finding is listed as resolved.
In practice:
› tavero_context
Action button is Button from src/components/Button.tsx (props: variant)
--acme-primary #3b5bdb · --acme-radius 10px · --acme-space 16px
› tavero_findings rule=colors
f_ff30adc07c Color #4263eb is not a project token
› tavero_finding f_ff30adc07c
src/app.css:33 literal #4263eb in source
expected --acme-primary #3b5bdb (ΔE 3.5)
edit src/app.css:33 background: #4263eb → var(--acme-primary)
› tavero_scan routes=/billing rules=colors
› tavero_diff
resolved f_ff30adc07c Color #4263eb is not a project token
resolved f_18862a0332 Hardcoded color #4263eb in 1 place(s)
That run was Claude Code with Opus 5.5, no shell, 29 seconds, 10 turns, on a copy of the Acme demo.
You decide what's intended
Not every difference is a bug. A red destructive button is probably on purpose. So there's a local panel for the person behind the agent: go through findings with the keyboard and mark each one:
- Fix — confirmed work; the agent sees it first.
- Intended — hidden from the agent until the finding grows.
- Canonical — the reference variant, with a name, used as the expected value.
You can attach a message to a finding, and each decision reaches the agent on its next call — no rescan needed.
Install
Add the MCP config to your client. For Claude Code (.mcp.json):
{
"mcpServers": {
"tavero": {
"type": "stdio",
"command": "npx",
"args": ["-y", "tavero", "mcp"]
}
}
}
Cursor uses the same block in .cursor/mcp.json. There are configs for Codex, Gemini CLI, Antigravity, VS Code and Windsurf too — tavero mcp --print-config prints all of them.
Then ask your agent to read tavero_context before it writes UI.
More, including the demo findings: https://taveroui.com
If you try it on your app, I'd love to hear what it finds.
Top comments (1)
A useful edge case for the scanner is two pages that intentionally use the same token but diverge only in an interaction state. For a primary button, compare the same route at default, disabled, focus, and loading states across widths; that catches an inline variant creeping in after the static screenshot looks canonical. Can Tavero inspect those state variations, or only the initial render?