Color System Repair
Description
Diagnose and repair a site's color system by consolidating a single neutral scale, mapping semantic tokens, and enforcing contrast and dark-mode consistency.
When to Use
I need to fix color chaos on my site | Consolidate my color palette into a single neutral scale | Map semantic colors for UI elements | Ensure accessible contrast and dark mode consistency | Audit a site for color consistency and tokens
Use Cases
Consolidate near-duplicate grays into one neutral scale | Map semantic colors for links, buttons, and alerts | Enforce accessible contrast for text and UI | Harmonize colors across light and dark modes | Replace hardcoded colors with design tokens
SKILL.md Content
---
name: color-system-repair
description: "Diagnose and repair a site's color system by consolidating a single neutral scale, mapping semantic tokens, and enforcing contrast and dark-mode consistency."
metadata:
tags: "visual-design, color-system, design-tokens, color-contrast, dark-mode, web-accessibility, ui-consistency"
source: "https://skilldb.dev/skills/web-polish-skills/color-system-repair"
pack: "web-polish-skills"
category: "Visual Arts & Design"
---
# Color System Repair
## When to use this skill
Use when the user says things like:
- "I need to fix color chaos on my site"
- "Consolidate my color palette into a single neutral scale"
- "Map semantic colors for UI elements"
- "Ensure accessible contrast and dark mode consistency"
- "Audit a site for color consistency and tokens"
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`, `#3f3f46` all 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
```bash
# 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:
```css
/* 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:
```css
: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:
```css
[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/20` looks different from `bg-blue-100`
and 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-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
handles 95% of UI needs.