Color System Repair
Fix color chaos in a vibecoded website — too many near-duplicate colors, inconsistent
Color chaos in vibecoded websites is an accumulation problem, not a design problem. Each AI prompt generates locally reasonable color values, but accumulated across dozens of prompts, the site ends up with thirty "slightly different grays" that make everything feel subtly wrong. The fix is not to redesign the palette but to consolidate: choose one neutral scale, assign semantic roles to every color, and mechanically replace every hardcoded value with a token reference. ## Key Points - **Near-duplicate values.** `#333333`, `#2d2d2d`, `#374151`, `#3f3f46` all used as "dark text" - **No semantic mapping.** The same blue is used for links, primary buttons, info alerts, and - **Orphan colors.** A green that appears exactly once on a success badge. A specific purple - **Contrast failures.** Light gray text on white backgrounds. Low-contrast placeholder text. - **Partial dark mode.** Some components respect dark mode, others have hardcoded white backgrounds. - Don't use opacity to create lighter shades. `bg-blue-500/20` looks different from `bg-blue-100` - Don't mix Tailwind color families (slate + gray + zinc in the same component). Pick one. - Don't use `text-gray-300` for text on white backgrounds. It fails contrast. - Don't hardcode dark mode colors. Use CSS variables that remap in dark context. - Don't create custom colors for one-off elements. Use the palette you have. - Don't use more than one accent color unless the brand requires it. One primary + neutral scale ## Quick Example ``` Slate (blue undertone) — Cool, techy, modern Gray (true neutral) — Safe, works everywhere Zinc (slightly warm) — Balanced, slightly sophisticated Neutral (pure neutral) — Clean, no bias Stone (warm undertone) — Earthy, editorial, warm ``` ``` #f9fafb, #fafafa, #f3f4f6, #f4f4f5 → var(--color-bg-secondary) #333, #374151, #27272a, #1f2937 → var(--color-text-primary) #6b7280, #71717a, #52525b → var(--color-text-secondary) #e5e7eb, #e4e4e7, #d4d4d8 → var(--color-border-default) ```
skilldb get web-polish-skills/Color System RepairFull skill: 186 linesColor System Repair
Core Philosophy
Color chaos in vibecoded websites is an accumulation problem, not a design problem. Each AI prompt generates locally reasonable color values, but accumulated across dozens of prompts, the site ends up with thirty "slightly different grays" that make everything feel subtly wrong. The fix is not to redesign the palette but to consolidate: choose one neutral scale, assign semantic roles to every color, and mechanically replace every hardcoded value with a token reference.
Every color in a healthy system must have a job. Background colors, text colors, border colors, interactive colors, and status colors each serve a distinct purpose. When the same blue is used for links, primary buttons, info alerts, and decorative backgrounds, changing any one of them requires changing all of them. Semantic color roles decouple visual appearance from usage, making the system maintainable and adaptable.
Contrast is accessibility, and accessibility is not optional. Every text-background combination must pass WCAG AA standards: 4.5:1 for normal text, 3:1 for large text. This is not a design preference -- it is a requirement that determines whether people with low vision can use your site. Checking contrast is a mechanical verification that should be part of every color system repair.
You are a color specialist who fixes the visual incoherence caused by vibecoded color
decisions. Every AI prompt generates its own color values — #f1f5f9 here, #f3f4f6
there, #fafafa somewhere else — and the accumulated result is a site with 30 "slightly
different grays" that make everything feel subtly wrong.
The Diagnosis
What Bad Color Looks Like
- Near-duplicate values.
#333333,#2d2d2d,#374151,#3f3f46all used as "dark text" across different components. The differences are invisible but the inconsistency is real. - No semantic mapping. The same blue is used for links, primary buttons, info alerts, and decorative backgrounds. When one needs to change, everything changes.
- Orphan colors. A green that appears exactly once on a success badge. A specific purple used in one header. Colors with no family or purpose.
- Contrast failures. Light gray text on white backgrounds. Low-contrast placeholder text. Colored buttons with unreadable text.
- Partial dark mode. Some components respect dark mode, others have hardcoded white backgrounds.
The Audit
# Extract every color value
grep -roh '#[0-9a-fA-F]\{3,8\}' src/ --include="*.tsx" --include="*.css" --include="*.scss" | sort | uniq -c | sort -rn
# Extract Tailwind color classes
grep -roh '\(bg\|text\|border\|ring\|shadow\)-\(gray\|slate\|zinc\|neutral\|stone\|red\|orange\|amber\|yellow\|lime\|green\|emerald\|teal\|cyan\|sky\|blue\|indigo\|violet\|purple\|fuchsia\|pink\|rose\)-[0-9]*' src/ | sort | uniq -c | sort -rn
# Find hardcoded colors (not using variables/tokens)
grep -rn 'color:\s*#' src/ --include="*.css" --include="*.scss"
grep -rn 'style.*color.*#' src/ --include="*.tsx" --include="*.jsx"
The Fix: A Minimal, Purposeful Palette
Step 1: Choose Your Neutral Scale
Every site needs exactly ONE neutral scale. Pick based on undertone:
Slate (blue undertone) — Cool, techy, modern
Gray (true neutral) — Safe, works everywhere
Zinc (slightly warm) — Balanced, slightly sophisticated
Neutral (pure neutral) — Clean, no bias
Stone (warm undertone) — Earthy, editorial, warm
Consolidate ALL grays/near-blacks/near-whites to your chosen scale:
/* BEFORE: 5 different "grays" from different Tailwind palettes */
.card { background: #f1f5f9; } /* slate-100 */
.sidebar { background: #f3f4f6; } /* gray-100 */
.panel { background: #fafafa; } /* neutral-50 */
.dropdown { background: #f4f4f5; } /* zinc-100 */
.tooltip { background: #f5f5f4; } /* stone-100 */
/* AFTER: one scale, one "light surface" token */
.card, .sidebar, .panel, .dropdown, .tooltip {
background: var(--color-bg-secondary); /* maps to zinc-100: #f4f4f5 */
}
Step 2: Define Color Roles
Every color must have a job. If it doesn't have a role, it doesn't belong:
:root {
/* === BACKGROUNDS === */
--color-bg-primary: #ffffff; /* Main content area */
--color-bg-secondary: var(--zinc-50); /* Cards, sidebars, sections */
--color-bg-tertiary: var(--zinc-100); /* Inset areas, table headers */
--color-bg-inverse: var(--zinc-900); /* Dark sections, tooltips */
/* === TEXT === */
--color-text-primary: var(--zinc-900); /* Headings, primary content */
--color-text-secondary: var(--zinc-600); /* Supporting text, descriptions */
--color-text-tertiary: var(--zinc-400); /* Placeholders, disabled text */
--color-text-inverse: #ffffff; /* Text on dark backgrounds */
/* === BORDERS === */
--color-border-default: var(--zinc-200); /* Cards, inputs, dividers */
--color-border-strong: var(--zinc-300); /* Emphasized borders */
--color-border-subtle: var(--zinc-100); /* Barely-visible dividers */
/* === INTERACTIVE === */
--color-primary-50: #eff6ff; /* Primary tinted background */
--color-primary-100: #dbeafe; /* Primary hover background */
--color-primary-500: #3b82f6; /* Primary default */
--color-primary-600: #2563eb; /* Primary hover */
--color-primary-700: #1d4ed8; /* Primary active */
/* === STATUS === */
--color-success-bg: #f0fdf4; --color-success: #22c55e; --color-success-text: #166534;
--color-warning-bg: #fffbeb; --color-warning: #f59e0b; --color-warning-text: #92400e;
--color-error-bg: #fef2f2; --color-error: #ef4444; --color-error-text: #991b1b;
--color-info-bg: #eff6ff; --color-info: #3b82f6; --color-info-text: #1e40af;
}
Step 3: Dark Mode (If Applicable)
Dark mode means remapping semantic tokens, not inverting colors:
[data-theme="dark"], .dark {
--color-bg-primary: var(--zinc-950);
--color-bg-secondary: var(--zinc-900);
--color-bg-tertiary: var(--zinc-800);
--color-bg-inverse: var(--zinc-100);
--color-text-primary: var(--zinc-50);
--color-text-secondary: var(--zinc-400);
--color-text-tertiary: var(--zinc-500);
--color-text-inverse: var(--zinc-900);
--color-border-default: var(--zinc-800);
--color-border-strong: var(--zinc-700);
--color-border-subtle: var(--zinc-800);
/* Primary gets slightly lighter for dark backgrounds */
--color-primary-500: #60a5fa;
--color-primary-600: #3b82f6;
}
Step 4: Contrast Verification
Check every text/background combination:
PASS: zinc-900 on white = 17.4:1 (AAA)
PASS: zinc-600 on white = 5.7:1 (AA)
PASS: zinc-400 on white = 3.5:1 (AA Large only — only for large text!)
FAIL: zinc-300 on white = 2.6:1 (FAIL — never use for text)
PASS: white on primary-600 = 7.8:1 (AAA)
PASS: white on primary-500 = 4.6:1 (AA)
FAIL: white on primary-400 = 3.2:1 (FAIL — button text won't be readable)
Tools: Use browser DevTools color picker, or a contrast checker extension. Every text element must pass WCAG AA (4.5:1 for normal text, 3:1 for large text).
Step 5: Mechanical Replacement
Replace hardcoded colors file by file:
#f9fafb, #fafafa, #f3f4f6, #f4f4f5 → var(--color-bg-secondary)
#333, #374151, #27272a, #1f2937 → var(--color-text-primary)
#6b7280, #71717a, #52525b → var(--color-text-secondary)
#e5e7eb, #e4e4e7, #d4d4d8 → var(--color-border-default)
Anti-Patterns
- Don't use opacity to create lighter shades.
bg-blue-500/20looks different frombg-blue-100and breaks when layered on non-white backgrounds. - Don't mix Tailwind color families (slate + gray + zinc in the same component). Pick one.
- Don't use
text-gray-300for text on white backgrounds. It fails contrast. - Don't hardcode dark mode colors. Use CSS variables that remap in dark context.
- Don't create custom colors for one-off elements. Use the palette you have.
- Don't use more than one accent color unless the brand requires it. One primary + neutral scale handles 95% of UI needs.
Install this skill directly: skilldb add web-polish-skills
Related Skills
Component Unification
Take scattered, inconsistent UI components from a vibecoded website and unify them into a
Dark Mode Retrofit
Add consistent dark mode to an existing website or fix partial/broken dark mode
Design Token Extraction
Extract a unified set of design tokens (colors, typography, spacing, shadows, radii) from a
Form Elements
Unify all form elements — inputs, selects, textareas, checkboxes, toggles, radio buttons,
Modal Dialog System
Unify all modals, dialogs, drawers, sheets, and overlay patterns across a website into a
Navigation Patterns
Fix inconsistent navigation patterns — mismatched headers, footers, sidebars, breadcrumbs,