Design system compliance for teams shipping with AI.
Drift reads the live React fiber tree, measures what percentage of your UI is built from your design system, and flags every custom component and hardcoded token — in dev, in CI, and on every PR.
AI coding tools (Cursor, Copilot, Claude) write components in seconds — but they don't know your design system. They invent one-off components, hardcode colors, and skip your spacing tokens. Sprint after sprint, your UI silently drifts from what was designed.
Drift is the guardrail. It gives every person on your team — designers, engineers, and PMs — a shared, measurable answer to: "how much has our UI drifted from the design system?"
Designer → Figma → Storybook (source of truth)
↓
Engineer vibes with Cursor / Claude
↓
Drift reads the live React fiber tree
↓
Coverage %, gaps, and token violations surfaced
↓
GitHub Action posts delta to every PR
design-drift/
├── src/
│ ├── ds-coverage/ ← THE TOOL (drop into any React app)
│ │ ├── DSCoverageOverlay.tsx Main panel UI (~1600 lines)
│ │ ├── fiberScanner.ts React fiber tree walker
│ │ ├── tokenChecker.ts Inline style / token violation detector
│ │ ├── manifest.ts DS component registry
│ │ └── config.ts ← Edit this to register YOUR components
│ │
│ ├── stories/ ← Sample design system (StorageOS)
│ │ ├── Button.tsx, Input.tsx, Modal.tsx, ...
│ │ └── prototypes/ Composed screen prototypes
│ │
│ ├── landing/
│ │ └── LandingPage.tsx Marketing site (served at /)
│ │
│ └── tokens/
│ ├── variables.css CSS custom properties (auto-generated)
│ └── tokens.ts Token constants (auto-generated)
│
├── scripts/
│ ├── drift-check.mjs CI scanner — headless Playwright scan
│ ├── figma-sync.mjs Figma API → tokens + icons pipeline
│ └── drift-mcp.mjs MCP server for IDE integration
│
├── api-proxy/
│ └── server.mjs Node proxy — keeps Anthropic key server-side
│
├── .claude/commands/ Claude Code slash commands
│ ├── drift.md
│ ├── drift-setup.md
│ ├── drift-sync.md
│ └── drift-push.md
│
└── .github/workflows/
└── drift-check.yml PR drift delta GitHub Action
For a backend developer: The files you need are
scripts/drift-check.mjs,.github/workflows/drift-check.yml, andsrc/ds-coverage/config.ts. You do not need to touchDSCoverageOverlay.tsxfor CI or backend work.
npm install @catchdrift/overlayCreate a config file at drift.config.ts in your project root:
import type { DesignDriftConfig } from '@catchdrift/overlay'
const config: DesignDriftConfig = {
// npm package, path prefix, or both — used to auto-discover DS components
dsPackages: ['@acme/ui'], // or ['./src/components']
storybookUrl: 'http://localhost:6006', // optional
figmaFileKey: 'your-figma-file-key', // optional
threshold: 80,
components: {
Button: { storyPath: 'primitives-button--primary' },
// auto-populated by `npx drift-sync` when dsPackages is set
},
}
export default configMount the overlay in your app entry point (dev only):
// main.tsx or App.tsx
import { DriftOverlay } from '@catchdrift/overlay'
function App() {
return (
<>
<YourApp />
{import.meta.env.DEV && <DriftOverlay />}
</>
)
}# Auto-discover DS components from your package or path prefix:
npx drift-sync
# Press D in the browser to open the Drift panelEdit src/ds-coverage/config.ts to register your design system components:
const config: DesignDriftConfig = {
storybookUrl: 'http://localhost:6006',
figmaFileKey: 'your-figma-file-key',
threshold: 80, // minimum DS coverage % to pass CI
components: {
Button: { storyPath: 'primitives-button--primary' },
Modal: { storyPath: 'primitives-modal--default' },
Navbar: { storyPath: 'shell-navbar--default' },
// ... one entry per component in your design system
},
}Each key must exactly match the React component's display name (visible in React DevTools).
npm run storybook # Component catalog → http://localhost:6006
npm run dev # Demo app → http://localhost:5173
npm run build # Production build
npm run drift-check # Run the CI scanner locally (requires a running app)
npm run figma-sync # Pull design tokens + icons from Figma
npm run drift-mcp # Start the MCP server for IDE integration
npm run proxy # Start the AI proxy (keeps Anthropic key server-side)
npm run chromatic # Publish Storybook to ChromaticIf you use Claude Code, these commands are pre-loaded in .claude/commands/:
/drift-setup Full interactive install wizard — sets up config, CLAUDE.md, CI, MCP
/drift Coverage report + gap analysis from terminal
/drift-sync Sync component registry from Figma and/or Storybook
/drift-push <Name> Push component spec back to Figma
Add to ~/.claude.json (or your IDE's MCP config):
{
"mcpServers": {
"drift": {
"command": "node",
"args": ["scripts/drift-mcp.mjs"],
"cwd": "/absolute/path/to/design-drift"
}
}
}Tools exposed: drift_manifest, drift_analyze, drift_gaps, drift_suggest, drift_report.
The GitHub Action at .github/workflows/drift-check.yml:
- Builds the app and starts a preview server
- Runs the headless Playwright scanner across configured routes
- Compares coverage against the last main-branch baseline
- Posts a formatted drift delta as a PR comment — updates on new commits, no spam
- Optionally fails CI if coverage drops below threshold
Repository variables (Settings → Variables → Actions):
| Variable | Default | Description |
|---|---|---|
DRIFT_THRESHOLD |
80 |
Minimum DS coverage % to pass |
DRIFT_ROUTES |
/ |
Comma-separated routes, e.g. /,/dashboard,/tenants |
DRIFT_MAX_DROP |
(unset) | Block PRs that drop coverage by more than this %. Set 0 for ratchet mode (coverage can never decrease). |
DRIFT_STRICT |
false |
Fail CI on any gap or token violation, regardless of % |
No secrets needed — uses the built-in GITHUB_TOKEN.
Example PR comment:
📊 Drift Delta 81% → 74% ⚠ −7% since main
Route: /dashboard — 🔴 74% DS coverage
DS components 10
Custom (drifted) 3
Token violations 19
Custom components:
• CustomCard ×3 (consider promoting to DS)
• InlineAlert ×1
export FIGMA_API_TOKEN=your_token
npm run figma-syncReads your Figma file (key in config.ts) and writes:
src/tokens/variables.css— CSS custom properties (--ds-color-*,--ds-spacing-*)src/tokens/tokens.ts— TypeScript constants
Never edit these files by hand — they are overwritten on every sync.
| UI framework | React 19 |
| Build tool | Vite 8 |
| Language | TypeScript 5.9 |
| Component catalog | Storybook 10 |
| CI scanner | Playwright (Chromium, headless) |
| Token pipeline | Style Dictionary 5 |
| AI features | Anthropic claude-haiku-4-5 |
| Styles | Inline styles + CSS custom properties only. No CSS files, no Tailwind. |
Why read the React fiber tree instead of the AST? Static analysis only sees source. The fiber tree shows what is actually rendered on screen — including lazy-loaded components, feature-flagged UI, and runtime-composed layouts. It is the only approach that catches drift in AI-generated code after it runs.
Why inline styles only?
Design system tokens (var(--ds-color-*), var(--ds-spacing-*)) must be the only styling source. CSS modules and utility classes create a styling layer that is invisible to the token checker. Inline styles make every violation detectable.
Why an overlay instead of a browser extension? Extensions require store review and cannot reliably access React internals on arbitrary domains. The overlay ships in minutes, works everywhere React runs, and is stripped completely in production builds.
| Component | Risk | Notes |
|---|---|---|
| GitHub Actions YAML | Very low | Actions API stable for years |
| Playwright / Chromium | Low | npm update quarterly |
React fiber key (__reactFiber$) |
Low | Unchanged since React 16; would only break on a major React version change |
| Token checker (inline style scan) | None | CSS property names don't change |
-
npm install @catchdrift/overlay— standalone npm package (v0.1.0) - VS Code extension — real-time token violation underlines as you type
- Pre-commit hook (
npx drift check) via Husky - Coverage history dashboard (requires backend + DB)
- GitHub App with OAuth (Team tier subscriptions)
- Multi-framework support (Vue, Svelte)
- Fork and clone
npm installnpm run dev— demo app on :5173npm run storybook— component catalog on :6006- Make changes, verify with
npm run build - Open a PR — the drift check runs automatically
The landing page (src/landing/) and the tool (src/ds-coverage/) are co-located for convenience. When Drift ships as an npm package the tool will move to its own repository.