The sameness is inherited, not invented
It is tempting to describe this as a taste problem — the model has seen a billion landing pages and regresses to their mean. That is part of it, but it is not the mechanism, and it leads people to the wrong fix (better prompts). The mechanism is more boring and much more actionable: a coding agent asked to build UI reaches for a component library, and that library ships defaults. Those defaults were designed to be inoffensive, which is exactly what makes them universal.
Ask five different tools — Claude Code, Cursor, v0, Lovable, Bolt — for a dashboard, with no other direction. You will get five layouts and one visual identity. They are not copying each other. They are each independently doing the sensible thing with the same starting material.
The four tells, and where each comes from
1. The neutral ramp
shadcn/ui's default theme is built on Tailwind's zinc scale, with slate and gray as the common swaps. It is a cool, slightly blue-tinted grey. Once you have noticed it you cannot stop noticing it: it is the background of most AI-built products shipped in the last two years. It is a genuinely good neutral, which is the problem — it is good enough that nobody replaces it.
The giveaway is not the grey itself but its *uniformity*. A designed interface usually biases its neutrals slightly toward the brand hue, so the greys feel related to the accent. A default interface has a pure ramp sitting next to an unrelated accent, and the two never quite belong to each other.
2. Inter, or something indistinguishable from it
Inter is an excellent UI typeface, which is why it is everywhere and why it now reads as a default rather than a decision. Where it is not literally Inter, it is the font-sans system stack, which lands on a near-identical grotesque on most machines. Either way the page has one family doing every job: headings, body, labels, numbers.
One family is the actual tell, more than which family it is. A designed system almost always pairs — a display face with character for headings against a workhorse for body, or at minimum a deliberate weight and width contrast within one superfamily. Single-family pages read flat even when every other decision is sound.
3. `0.5rem` on everything
shadcn's default --radius is 0.5rem, and it propagates to buttons, inputs, cards, dialogs and popovers alike. Uniform radius across every element size is not how designed systems behave: a 44px button and a 400px card carrying the identical corner radius makes the card look soft and the button look blobby, because radius reads relative to the element it is on.
:root {
/* The line most AI-built projects never touch */
--radius: 0.5rem;
}4. The three-card grid
Hero, then three equal cards, then a testimonial band, then a CTA. This one is genuinely training-data gravity rather than a config default — it is the single most common marketing-page structure on the web, so it is the shape a model produces when the brief does not imply another one. It is not wrong. It is simply the layout that carries no information about your product.
Why better prompting stops working
The common response is to write a longer prompt: *use a warm palette, pair a serif display with a grotesque body, vary the radius.* That works. It works for one screen, and it usually works for the second and third.
Then it degrades, and it degrades in a specific way. The agent is not ignoring your instruction — it is holding an increasingly long conversation in which your styling paragraph is competing with everything since. By screen five it remembers *warm* and has lost the hex values, so it approximates. The approximation is a slightly different warm. Screen six approximates the approximation.
Drift is not the agent forgetting your instruction. It is the agent remembering it approximately, six times in a row.
People in the middle of this call it *design drift*, and it is the reason a vibe-coded app that looked sharp on Friday looks assembled by committee on Monday. The instruction lived in the conversation, and conversations decay.
The test that separates the two failure modes: open screen one and screen eight side by side. If they disagree about spacing and colour but each is internally consistent, that is drift. If both are internally inconsistent, the system was never specified at all.
The fix is a file the agent re-reads, not a paragraph it remembers
A design system stops drift for the same reason it stops it on human teams: the values live somewhere durable and get consulted, rather than being carried in someone's head. For a coding agent, that somewhere is a DESIGN.md plus a token set in the repo. Both are re-read at the start of every session, so screen twenty resolves the same --primary as screen one — not a similar one.
| In the prompt | In tokens + DESIGN.md | |
|---|---|---|
| Survives a new session | No — context resets | Yes — re-read from the repo |
| Precision after 10 screens | Approximate ('warm-ish') | Exact hex, every time |
| Reviewable in a PR | No | Yes — it is a file that diffs |
| Works across tools | Rewrite per tool | Same file for Claude Code, Cursor, v0 |
This is the whole argument for giving an agent a real system rather than a better description of one. The semantic token layer matters more than the palette: --primary and --muted-foreground survive a redesign, whereas blue-600 scattered through markup does not.
Token specimen · real values
Terrain Vivant
Live renderTerrain Vivant's actual tokens — the same values its exports use.
Color tokens
Terrain Vivant
Core
background
H 131 · C79, 0, 64, 38
foreground
H 120 · C100, 0, 100, 92
card
H 235 · C68, 62, 0, 47
muted
H 130 · C83, 0, 69, 47
border
H 134 · C89, 0, 69, 56
Brand
primary
H 235 · C68, 62, 0, 47
primary-fg
H 0 · C0, 0, 0, 0
secondary
H 215 · C81, 47, 0, 45
accent
H 209 · C95, 46, 0, 33
ring
H 235 · C68, 62, 0, 47
Semantic
destructive
H 0 · C0, 85, 85, 27
destructive-fg
H 0 · C0, 0, 0, 0
success
H 132 · C100, 0, 79, 70
warning
H 23 · C0, 56, 90, 43
muted-fg
H 0 · C0, 0, 0, 100
Charts
chart-1
H 235 · C68, 62, 0, 47
chart-2
H 209 · C95, 46, 0, 33
chart-3
H 210 · C92, 46, 0, 39
chart-4
H 120 · C100, 0, 100, 92
chart-5
H 235 · C57, 51, 0, 46
Typography
Terrain Vivant
Scale: perfect-fourth
Density: balanced
Heading · Space Mono · 3rem
Sample headline
Subheading · Space Mono · 2.25rem
A bold two-color institutional editorial system built on vivid green and cobalt blue full-screen surfaces, with monospace type throughout and zero-radius geometry.
Body · Space Mono · 1rem
A resolutely flat two-color surface system. Vivid grass-green (#229f39) and deep cobalt-blue (#2b3386) are sovereign peers: either can fill an entire screen or section, with no neutral intermediary. All text is set in Space Mono at every scale from hero display down to captions, making the monospace grid the primary typographic voice rather than a code aesthetic. All shapes are sharp 90-degree rectangles, and section breaks use stacked parallel horizontal lines as a graphic band.
Mono · Space Mono · 0.75rem
npx shadcn add terrainvivant.json
Aa
Space Mono · Heading
Aa
Space Mono · Body
ABCDEFGHIJKLM NOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
0123456789 & @ # % →
Do the people who write it down actually escape the defaults?
Mostly not, and that is the more uncomfortable finding. We sampled 299 public DESIGN.md files from GitHub — the file whose entire job is to stop this — and measured what was in them.
Of the 72 that describe a visual design system, 21% specify a radius scale and 26% specify elevation. Those are two of the four tells above, left at their defaults by three quarters of the people who sat down specifically to write a design contract. 69% omit dark mode entirely, so the whole second theme is left to the agent to invent. And 86% name colours by hue or list bare hexes rather than using semantic roles, which means the file cannot be applied to any component it did not explicitly describe.
The defaults survive the attempt to replace them
Typography is covered in 83% of files and colour in 67%, because those are the two things people think of as 'design'. Radius, elevation and dark mode are the ones that quietly stay generic. The four tells persist not because nobody tried, but because most attempts cover the visible half.
There is a second-order version of the same problem: 54% of files contain at least one vague adjective standing in for a value, led by *clean* (39%) and *modern* (36%). An adjective does not replace a default. It asks the agent to pick one.
A five-minute audit
- 1
Sample the background
Pull the hex of your page background. If it is
#09090b,#18181b,#0f172aor#020617, you are on an untouched zinc or slate ramp. - 2
Count type families
Open the computed styles on an H1 and on a body paragraph. Same
font-familyon both means one family is doing every job. - 3
Compare two radii
Measure the corner radius on a button and on your largest card. Identical values mean nobody scaled radius to element size.
- 4
Diff your first and last screen
Put the earliest screen you built next to the most recent. List every value that disagrees — that list is your drift.
- 5
Decide where the answer lives
For each disagreement, ask whether the correct value exists anywhere durable. If the honest answer is 'in the prompt I wrote in March', that is the thing to fix.
Skip the four defaults entirely
Every Identity Forge kit ships a full token set — neutrals tuned to the accent, a real font pairing, radius scaled per element — plus the DESIGN.md your agent reads before it writes markup. Free kits need no account.
Is this a problem with the model, or with the tools around it?
Mostly the tools. The same model produces distinctive UI when it is handed a token set and a brief, and generic UI when it is handed neither. What varies is the starting material, not the capability.
Does changing the accent colour fix it?
No, and this is the most common wasted afternoon. Swapping the accent while keeping the default neutrals, single type family and uniform radius produces the same page in a different hue. Type and structure carry more of the identity than the accent does.
Can I just tell the agent to avoid Inter and zinc?
For one session, yes. The instruction lives in context, so it degrades as the conversation grows and disappears entirely when you open a new one. A file in the repo does not have that property.
Is shadcn/ui the problem?
No. shadcn ships sensible defaults precisely so you can start without deciding everything, and its theming layer is what makes the fix straightforward. The problem is that the defaults are treated as finished rather than as a starting point.
How many screens before drift becomes visible?
In our own runs it shows up around the fifth or sixth screen of a continuous session, and much earlier if you switch tools midway. There is no fixed number — it tracks conversation length, not screen count.