Design drift is the gradual divergence of a product from its own design language: three slightly different buttons, spacing that follows the scale in some screens and not others, two ways of showing an empty state. AI coding agents speed it up, because each session fills gaps independently. A drift audit finds it systematically: inventory the values and components in use, capture every screen, compare like with like, rank what you find, then fix the system before fixing the screens.
This is a one-off clean-up. Preventing drift from coming back is covered in how to keep AI coding agents consistent across a product.
Signs you need an audit
- New screens feel like a different product from older ones.
- Reviews keep catching the same small inconsistencies.
- The component folder has several things that do the same job.
- Nobody can say which of two patterns is “right”.
Step 1: inventory the values
Start with the code, because it’s exhaustive. An agent is good at this kind of tedious enumeration. Ask for a report, not changes:
Without changing anything, inventory our visual values in src/:
- every colour (hex, rgb, oklch, Tailwind palette classes, arbitrary values)
- every font size, weight, line height and letter spacing
- every spacing value outside our scale, and every arbitrary value like p-[18px]
- every border radius and shadow
For each: count, files, and the nearest token. Group near-duplicates.
Save the result to docs/audit/values.md.If you use Tailwind, the dependency-free check in design tokens for AI coding agents finds arbitrary values and raw colours mechanically, and can stay in CI afterwards.
Step 2: inventory the components
List every component that renders a button, input, select, dialog,
menu, table, tab, badge or card, wherever it's defined. For each,
show where it's used and how it differs from the version in
src/components/ui. Flag components that duplicate a library
component with small differences. Save to docs/audit/components.md.Near-duplicates are the most expensive form of drift: every one is another place a future change has to be made, and another example an agent might copy.
Step 3: capture every screen
Code inventories miss composition: two screens can use the same tokens and still feel different. Capture every route at a desktop and a phone width. Playwright makes this repeatable; our visual testing guide has a tested setup. A minimal capture script needs only a list of routes:
import { test } from "@playwright/test";
const routes = ["/", "/projects", "/settings", "/settings/billing"];
for (const route of routes) {
test(`capture ${route}`, async ({ page }, testInfo) => {
await page.goto(route);
await page.waitForLoadState("networkidle");
const name = route === "/" ? "home" : route.slice(1).replaceAll("/", "-");
await page.screenshot({
path: `docs/audit/screens/${testInfo.project.name}-${name}.png`,
fullPage: true,
});
});
}Run it with the desktop and mobile projects from your Playwright config, and you have a folder of every screen side by side.
Step 4: compare like with like
Drift shows up when you put the same pattern from different screens next to each other. Group the screenshots by pattern rather than by page:
- Page headers: title size, description, where actions sit.
- Tables and lists: row height, alignment, how metadata is muted.
- Forms: label position, spacing, error display.
- Empty, loading and error states: usually the most inconsistent of all.
- Dialogs and sheets: width, button order, close behaviour.
Look at the screenshots in docs/audit/screens. For page headers,
tables, forms, empty states and dialogs, describe how each is
handled on every screen where it appears. Point out where the same
pattern is done differently, and which version is most common.Most common isn’t the same as best. The agent can find the variants; deciding which one represents the product is a design call.
Step 5: rank what you found
Not all drift is worth fixing at once. Rank by impact:
- Accessibility failures: contrast, focus, labels. Fix regardless.
- Duplicate components: fix early; they multiply.
- Inconsistent core patterns: headers, tables, forms seen on many screens.
- Stray values: off-scale spacing and one-off colours.
- Local oddities: a single screen that’s slightly off.
Step 6: fix the system, then the screens
Decide the canonical version of each pattern and write it down before changing any screen. Then update the system (tokens, components, instructions) so the decision is enforced, and only then migrate screens, one pattern at a time, in small reviewable changes.
The canonical empty state is the one on /projects (see
docs/audit/decisions.md). Extract it into src/components/ui/empty-state.tsx
with props for title, description and one action. Then replace the
other empty states one file at a time, screenshot each before and
after, and stop after three files for my review.Record each decision in your instruction file so the next agent session follows it. Our guide to CLAUDE.md for UI design shows where those rules go.
Step 7: keep a baseline
End the audit with visual baselines for the key screens and the token check in CI. The next audit then starts from a known state, and drift shows up as a failing check rather than a feeling.
Where the drift came from
An audit usually shows that drift traces back to direction the agent never had: which empty state is canonical, which references represent the product, which patterns to avoid. In Attuna, which is in private beta, that direction lives in a project as rules to prefer or avoid, preferences and references, and agents read it over MCP before they build. Rules an agent suggests wait for your approval, so the project stays yours.