Design drift: why your vibe-coded app falls apart at screen five

Screen one looked sharp. Screen eight looks like a different product built by someone who had heard a description of screen one. The phrase people reach for is design drift, and unlike most complaints about AI-built UI it is something you can actually count.

Updated July 27, 2026

What drift actually is

Drift is not the agent ignoring you and it is not a quality problem in any single screen. Each screen is usually fine on its own. The failure is *relational*: screen eight disagrees with screen one about values that ought to be constant.

That distinction matters because it decides the fix. A quality problem is fixed with better guidance. A consistency problem is fixed with durable values — no amount of additional taste makes two independent judgments land on the same hex.

Every screen is fine. The product is not. That gap is the whole of design drift.

Why it starts around screen five

There is nothing magic about five, and the real variable is conversation length rather than screen count. Long sessions build long context, and your styling paragraph from the beginning is competing with everything since.

What degrades is specific and predictable. Precise values go first, because they are arbitrary strings with no semantic anchor: oklch(0.55 0.19 45) is far harder to retain than *warm orange*. So the agent keeps the adjective, discards the value, and re-derives a warm orange when it next needs one. That new value becomes the reference for the screen after.

  1. Spacing and radius go first. Nobody names them in the brief, so they are re-derived from scratch every time.
  2. Type scale goes second. Base size tends to survive; the ratio between steps does not.
  3. Accent colour goes last. It is usually mentioned in the brief, which anchors it — this is why people underestimate drift by only checking colour.
  4. Layout conventions barely drift at all, which is why the app still feels coherent while its details disagree.

Tool switching skips the gradual part

Move from Cursor to Claude Code mid-project and every value not written to a file resets at once. The second tool has no access to the first tool's conversation, so it starts from defaults — which is why drift often shows up as a sudden break rather than a slope.

Measuring it

Drift is unusual among UI complaints in being straightforwardly countable. Pick the values that should be invariant, sample them on your earliest and latest screens, and count the disagreements.

  1. 1

    Choose your invariants

    Six is enough: primary colour, page background, base font size, type scale ratio, card radius, and the standard gap between a label and its control.

  2. 2

    Sample the earliest screen

    Use computed styles rather than the source, so you capture what actually renders after the cascade.

    getComputedStyle(document.querySelector('[data-primary-action]')).backgroundColor
  3. 3

    Sample the latest screen

    The same six values, the same way.

  4. 4

    Count disagreements

    That count out of six is your drift score. Zero means the values are coming from somewhere durable; four or more means they are being re-decided per screen.

  5. 5

    Repeat on a middle screen

    This tells you whether drift is gradual or whether it broke at one point — usually a tool switch or a long gap between sessions.

Design driftNever specified
Any single screenInternally consistentInternally inconsistent
First vs latest screenDisagreeBoth disagree with themselves
CauseValues decayed from contextNo values ever existed
FixMove values into filesDecide the system first
The two failure modes look similar from a distance and need opposite fixes.

Why the usual workarounds disappoint

  • Restating the palette every few prompts. Works, but you are now the memory, and you will forget before the agent does.
  • One very long system prompt. Helps early, then contributes to the context pressure causing the problem.
  • Asking the agent to match an existing screen. It has to infer values from markup it may not have in context, and inference introduces its own error.
  • Refactoring at the end. The most expensive option: you pay to build inconsistency and then pay again to remove it.

All four share a shape. They try to keep values alive inside the conversation, and conversations are the thing that decays.

The fix, and why it works

Move the invariants out of the conversation and into the repository: semantic tokens plus a DESIGN.md. The agent reads them at the start of every session, so screen twenty resolves the same --primary as screen one rather than a near neighbour.

Semantic naming is doing real work here beyond tidiness. --primary and --muted-foreground describe roles, so the agent can apply them correctly in situations nobody anticipated. A raw hex has to be remembered *and* correctly placed; a role only has to be looked up. That is the argument for a semantic token layer rather than a palette.

  1. 1

    Install the contract

    npx --yes identityforge@latest install --client claude-code
  2. 2

    Apply a kit

    Writes DESIGN.md plus light and dark tokens into the repo.

    identityforge apply quiet-matter
  3. 3

    Make the rule a prohibition

    Prohibitions are followed more reliably than preferences.

    Never hardcode theme colors, spacing or radii. Use the tokens in DESIGN.md.
  4. 4

    Re-measure

    Run the six-value diff again after the next two screens. A drift score of zero is the expected result, and if it is not, something is still being decided outside the token layer.

Does a design system solve drift on its own?

Partly, and the part it misses is the part that hurts. A component library fixes component-level consistency: a Button is a Button on every screen. It does nothing about the theme values those components read, and that is where drift actually lives.

There is a sharper version of this problem for teams who already have a system. The most common question people ask about design skills and agent tooling is whether an agent will *respect* an existing theme or quietly invent alongside it. The answer depends entirely on whether the theme exists as values the agent can read, or only as a Figma library and a shared understanding.

Fixed by a component libraryStill drifts
Component internalsYes — a Button is a Button
Theme values the components readNoPrimary, background, radius, spacing
Composition between componentsNoSection rhythm, hierarchy, grid choices
Anything the library does not coverNoInvented per screen, differently each time
Where a design system stops and where drift continues.

The failure a Figma library cannot prevent

If your system lives in Figma and in people's heads, a coding agent cannot read it. It will produce components that look plausible and use values nobody chose. A design system stops drift only to the extent it exists as text in the repository.

Does drift differ between Cursor, Claude Code, v0 and Lovable?

The mechanism is the same everywhere, because it is context decay rather than a defect in any one tool. What differs is how much durable state each tool can read, and that changes where drift bites.

  • Repo-based agents (Claude Code, Cursor). They can read files, so a DESIGN.md plus tokens genuinely fixes it. Their weakness is that nothing forces them to look, which is why the instruction belongs in AGENTS.md as a prohibition.
  • Prompt-based builders (v0, Lovable, Bolt). Less repository to read from, so more of the identity has to be restated. Paste the token block rather than describing it.
  • Switching between any two of them. The sharpest break, because the second tool has no access to the first tool's conversation and starts from library defaults.

This is why the fix is a file rather than a tool choice. A file is the one thing all of them can consume, and the one thing that survives you changing your mind about which to use.

Where drift does the most damage: dark mode

We sampled 299 public DESIGN.md files from GitHub and measured what was in them. Of the 72 that describe a visual design system, 69% contain no dark mode at all — no second set of values, no prefers-color-scheme, nothing.

That matters here specifically. Where a value is absent from durable state, the agent invents it, and invented values are exactly what drift is made of. A file that covers light mode fully and omits dark mode is not half-protected; it is fully protected in one theme and completely undefended in the other. Your drift score can read zero in light mode and four in dark.

When you run the six-value diff, run it twice — once per theme. Dark mode is where the numbers usually part company, and it is the measurement people skip because the light screens look fine.

Preview unavailable here. Browse complete kits in the kit gallery.

What is worth noticing is how *reasonable* the drifting side looks in isolation. Drift is never obviously wrong at the component level; it is only visible when two screens are put next to each other, which is precisely the comparison nobody makes during a build.

Stop paying for drift twice

Install a kit before the next screen instead of refactoring after the twentieth. Tokens, DESIGN.md and a shadcn registry item, in one command. Free kits need no account.

Is drift a sign I picked the wrong model?

No. It shows up across every agent because the cause is context decay rather than capability. A stronger model holds values slightly longer and then drifts in the same way.

Can I avoid it by keeping sessions short?

It helps within a session and makes the cross-session problem worse. Each new session starts from defaults, so short sessions without durable values give you sharper breaks more often.

Does a component library solve this?

Partly. It fixes component-level consistency, since a Button is a Button. It does not fix the theme values those components read, which is where drift actually lives.

My drift score is zero but the app still looks inconsistent. What now?

Then you have the other failure mode: the values are stable but the system is thin. Look at hierarchy, spacing rhythm within a screen, and whether one type family is doing every job.

How often should I re-measure?

After any tool switch, after any gap of more than a few days, and before shipping. Those are the three points where unwritten values are most likely to have reset.