You find a hero block you like. You paste the link into Cursor or Claude Code and ask for "this hero on my landing page". A minute later you have a hero: different spacing, a different gradient, no entrance animation. It looks like every other AI-generated landing page.
I build a component registry for this exact workflow, and I spent a long time finding out where the design gets lost. Short version: not in the code. It gets lost in the summary.
The flow that should work
pick a component → copy an instruction → paste it into the agent
→ agent runs `npx shadcn add <url>` → your project has exactly what the preview showed
The instruction is generated from the component's metadata, not hand-written: component ID, install command, dependencies, what to keep (motion, typography, spacing, responsive behaviour), what may change (copy, links, accent colour), and an explicit rule not to rebuild it from scratch.
On paper that's enough. In practice it broke in two places.
Failure 1: the summary eats the rules
When an agent "reads" a URL, it usually doesn't read it. The web-fetch tool passes the page through a small model and returns a digest. Commands and facts survive the digest. Prose rules — "keep the entrance animation", "don't change the grid" — are the first thing to go.
So the agent installs the component correctly… and then "improves" it, because the part that said don't never reached it.
Failure 2: the generic reflex
If the instruction describes the component ("a hero with a gradient and two buttons"), the agent writes a hero with a gradient and two buttons. Itself. A description is a spec, and an agent with a spec builds.
So the instruction should not describe the look at all. It should say: here's the command, here's the source, install it.
The fix: send the rules three ways
Putting 2–3 "keep as installed" rules in the brief is not enough. Duplicate them into places nobody summarises.
1. The brief. Full text, for the human and (best case) the agent.
2. The docs field of the registry item. The shadcn CLI prints it to the terminal after install. The agent sees it as command output, verbatim, no summary in between. Here is a real one from https://vibeui.club/r/en/hero-001.json:
Keep as installed:
- the button-016, heading-001 imports from @/components/vibeui/ — do not inline
their code into the block or swap in your own; the parts receive props only
- light-dark() in the palette: the component has to follow the surrounding
color-scheme rather than ship a dark theme of its own
- the transparent background by default — the component sits on the page
background instead of laying a panel under itself
3. A comment at the top of the file. The file is written byte for byte, so the rules end up inside the code the agent is about to edit:
"use client"
// VibeUI hero-001. Keep as installed:
// - the button-016, heading-001 imports ...
// - light-dark() in the palette ...
One gotcha: the shadcn CLI runs files that start with a "use client" directive through ts-morph and drops comments that sit before the directive. The header has to go after it. The whole thing is about 20 lines:
export function withPreserveHeader(source: string, rules: string[], name: string) {
if (rules.length === 0) return source
const header = [`// VibeUI ${name}. Keep as installed:`, ...rules.map((r) => `// - ${r}`), "", ""].join("\n")
// comments before "use client" are lost by the CLI, so the header goes after it
const directive = /^(["']use client["'];?\r?\n)\r?\n?/.exec(source)
return directive ? `${directive[1]}\n${header}${source.slice(directive[0].length)}` : header + source
}
Rules don't help if the component breaks in someone else's project
The agent will drop your component into a project you've never seen. What turned out to be mandatory:
-
A local palette in
--vibeui-<name>-*variables instead of the host's theme tokens. Otherwise the block gets repainted by someone else's theme and the agent "fixes" it. -
light-dark()instead of a custom dark theme. The component follows the page'scolor-scheme. For next-themes / shadcn, which toggle a.darkclass, one line setscolor-scheme: darkunder it. - Layout by its own width (container queries), not the viewport. Blocks often land in a column.
-
Zero npm dependencies where possible, and
prefers-reduced-motioninside the component, soglobals.cssneeds no edits. -
Composite blocks install their parts with one command via
registryDependenciesinstead of inlining their code. Thedocsfield above tells the agent not to inline them either.
Checklist if you run your own registry
- Write the agent instruction as a command, not a description.
- Put the critical rules where there is no summary: CLI output (
docs) and the file itself. - Check that your build tool doesn't eat the comments.
- Make the component independent of the host theme and the parent's width.
I use this setup in VibeUI: a shadcn-compatible catalog of 2,000+ React/Tailwind components and landing blocks where every item has a "Copy for AI" brief. Previews and search are open without an account. If you've solved the "agent rewrites my component" problem differently, I'd like to hear how in the comments.
Top comments (0)