Design Debt Management
Tracking and paying down the gap between the design system and what shipped.
Visual DesignIntermediate
Why
Every ship-under-deadline decision creates debt: a one-off color, a hardcoded spacing, a component that should have been in the system. Untracked, it compounds until the UI is inconsistent and the system is irrelevant.
Google's research shows users form a first impression in 50ms, and 75% of judgments about website credibility are based on visual aesthetics. Consistent spacing scales (8px grid) reduce cognitive load measurably.
When
Continuously. Log debt as it's created, schedule a pay-down sprint each quarter, and track the debt burndown like a tech-debt burndown.
How
1. Log it — when you ship a one-off, add a ticket: "Replace #FF0000 in PromoBanner with --color-promo" tagged design-debt
2. Categorize — token debt (wrong color/spacing), component debt (should be in the system), pattern debt (inconsistent UX)
3. Prioritize — user-facing inconsistency (two button styles) > invisible debt (refactor) > nice-to-have
4. Pay-down sprint — dedicate 10–20% of each quarter to design debt; don't let it grow unbounded
5. Lint — stylelint bans raw colors/spacing; new debt can't ship without an explicit override
6. Audit — run the audit quarterly (visual regression + token usage report); surface the top 10 drifts
7. Sunset — when a component is replaced, track its usage; remove only when usage hits 0
Example
// design-debt log — a markdown file or Linear project tagged "design-debt"
/*
- [ ] token: replace #FF0000 in PromoBanner with --color-promo-500 (added 2026-01-15, #PR-1234)
- [ ] component: extract PromoBanner into the system (3 one-off instances)
- [ ] pattern: unify toast positioning — 3 components use top-right, 2 use bottom-center
- [ ] spacing: replace 17px gap in UserCard with --space-4 (16px)
*/
// stylelint — prevent new token debt
"declaration-property-value-allowed-list": {
"color": ["/var\\(--color-/"],
"gap": ["/var\\(--space-/"],
}
// Quarterly audit script — find raw values that should be tokens
const rawColors = await grep(/#[0-9a-f]{6}/i, "src/**/*.{tsx,css}");
const rawSpacing = await grep(/\b\d+px/g, "src/**/*.{tsx,css}"); // except 1px borders
console.log(`${rawColors.length} raw colors, ${rawSpacing.length} raw spacings — design debt`);
Common Problems
- ⚠Debt not logged — one-off ships, no ticket, forgotten until a designer notices the drift months later
- ⚠No pay-down time — debt grows every sprint, never a scheduled window to fix it
- ⚠No lint prevention — new raw colors/spacing ship freely, debt compounds even while paying down
Best Practices
- ✓Log debt when it's created — a ticket at PR time, not 'we'll fix it later' (you won't)
- ✓Schedule a pay-down sprint each quarter — 10–20% of capacity, treat it like tech debt
- ✓Add stylelint bans for raw colors/spacing — prevent new debt; overrides need an explicit reason