Dark Mode Retrofit
0
Upvotes
4
Views
0
Downloads
1,185
Words
Description
The Dark Mode Retrofit skill adds or fixes dark mode on existing websites, delivering a complete, consistent, and accessible theme.
When to Use
I need to retrofit dark mode on an existing site. | Partial dark mode exists and isn't consistent. | Convert hardcoded colors to CSS variables for dark mode. | Add a theme toggle with persistence. | Audit color contrast for dark mode.
Use Cases
Add full dark mode to an existing site. | Replace hardcoded colors with CSS vars for dark mode. | Add a theme toggle with persistence. | Audit and fix dark mode contrast and readability.
SKILL.md Content
---
name: dark-mode-retrofit
description: "The Dark Mode Retrofit skill adds or fixes dark mode on existing websites, delivering a complete, consistent, and accessible theme."
metadata:
tags: "visual-design, dark-mode, css-variables, theming, color-contrast, ui-consistency, web-accessibility"
source: "https://skilldb.dev/skills/web-polish-skills/dark-mode-retrofit"
pack: "web-polish-skills"
category: "Visual Arts & Design"
---
# Dark Mode Retrofit
## When to use this skill
Use when the user says things like:
- "I need to retrofit dark mode on an existing site."
- "Partial dark mode exists and isn't consistent."
- "Convert hardcoded colors to CSS variables for dark mode."
- "Add a theme toggle with persistence."
- "Audit color contrast for dark mode."
You are a theming specialist who adds or fixes dark mode in existing websites. Partial
dark mode — where some components flip and others stay white — is worse than no dark mode
at all. You make dark mode complete, consistent, and correct.
## Assessment: Where Are You Starting?
### Scenario A: No Dark Mode Exists
The site uses hardcoded colors everywhere. You need to:
1. Extract all colors into CSS custom properties
2. Create light and dark value sets
3. Add a theme toggle and persistence
### Scenario B: Partial Dark Mode
Some components have `dark:` classes, others don't. Some CSS variables exist, some values
are hardcoded. You need to:
1. Find every component that doesn't respect dark mode
2. Replace hardcoded colors with variables
3. Ensure consistency
### Scenario C: Dark Mode Exists but Looks Bad
Values are technically set but colors are wrong — text is unreadable, contrast is poor,
elements disappear against backgrounds. You need to:
1. Audit every color pairing for contrast
2. Fix problematic mappings
3. Handle images, shadows, and borders
## Implementation Strategy
### Step 1: Theme Architecture
```css
/* Use CSS custom properties with data attribute switching */
:root {
color-scheme: light dark;
/* Light mode (default) */
--bg-primary: #ffffff;
--bg-secondary: #f4f4f5;
--bg-tertiary: #e4e4e7;
--bg-elevated: #ffffff;
--text-primary: #18181b;
--text-secondary: #52525b;
--text-tertiary: #a1a1aa;
--text-inverse: #ffffff;
--border-default: #e4e4e7;
--border-strong: #d4d4d8;
--shadow-sm: 0 1px 2px rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px rgb(0 0 0 / 0.07);
--shadow-lg: 0 10px 15px rgb(0 0 0 / 0.1);
--primary-500: #3b82f6;
--primary-600: #2563eb;
}
[data-theme="dark"] {
--bg-primary: #09090b;
--bg-secondary: #18181b;
--bg-tertiary: #27272a;
--bg-elevated: #1c1c1e; /* Cards/modals sit slightly above background */
--text-primary: #fafafa;
--text-secondary: #a1a1aa;
--text-tertiary: #71717a;
--text-inverse: #18181b;
--border-default: #27272a;
--border-strong: #3f3f46;
/* Shadows are more subtle in dark mode */
--shadow-sm: 0 1px 2px rgb(0 0 0 / 0.2);
--shadow-md: 0 4px 6px rgb(0 0 0 / 0.3);
--shadow-lg: 0 10px 15px rgb(0 0 0 / 0.4);
/* Primary colors shift slightly for dark backgrounds */
--primary-500: #60a5fa;
--primary-600: #3b82f6;
}
```
### Step 2: Theme Toggle
```tsx
// hooks/useTheme.ts
export function useTheme() {
const [theme, setTheme] = useState<'light' | 'dark'>(() => {
if (typeof window === 'undefined') return 'light';
return localStorage.getItem('theme') as 'light' | 'dark'
|| (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
});
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}, [theme]);
const toggle = () => setTheme(t => t === 'light' ? 'dark' : 'light');
return { theme, toggle, setTheme };
}
// components/ThemeToggle.tsx
import { Sun, Moon } from 'lucide-react';
export function ThemeToggle() {
const { theme, toggle } = useTheme();
return (
<button
onClick={toggle}
className="p-2 rounded-md text-gray-500 hover:bg-gray-100 dark:hover:bg-zinc-800 transition-colors"
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
>
{theme === 'light' ? <Moon className="h-5 w-5" /> : <Sun className="h-5 w-5" />}
</button>
);
}
```
### Step 3: Component-by-Component Migration
For each component, replace hardcoded colors with CSS variables:
```css
/* BEFORE */
.card {
background: white;
border: 1px solid #e5e7eb;
color: #111827;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* AFTER */
.card {
background: var(--bg-elevated);
border: 1px solid var(--border-default);
color: var(--text-primary);
box-shadow: var(--shadow-sm);
}
```
Or with Tailwind (if using `dark:` variant):
```tsx
// BEFORE
<div className="bg-white border border-gray-200 text-gray-900">
// AFTER
<div className="bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 text-gray-900 dark:text-zinc-100">
```
**The CSS variable approach is better for retrofits** because you change values in one place
instead of adding `dark:` to every className in every component.
### Step 4: Handle Special Cases
**Images:**
```css
/* Reduce image brightness slightly in dark mode */
[data-theme="dark"] img:not([data-preserve-color]) {
filter: brightness(0.9);
}
/* Invert dark logos/icons for dark mode */
[data-theme="dark"] .logo-dark {
display: none;
}
[data-theme="dark"] .logo-light {
display: block;
}
```
**Shadows:**
Dark mode shadows should be darker and more diffuse. They're sitting on a dark background,
so the light-mode subtle shadow becomes invisible.
**Borders:**
Borders become MORE important in dark mode (they define containers where shadows can't),
but must use darker values to avoid looking harsh.
**Code blocks:**
```css
[data-theme="dark"] pre, [data-theme="dark"] code {
background: var(--bg-tertiary);
/* Or use a dedicated code theme */
}
```
**Input fields:**
```css
[data-theme="dark"] input, [data-theme="dark"] select, [data-theme="dark"] textarea {
background: var(--bg-secondary);
border-color: var(--border-default);
color: var(--text-primary);
}
```
**Charts and data visualization:**
```css
/* Charts need adjusted colors for readability on dark backgrounds */
[data-theme="dark"] .chart-line { stroke: var(--primary-400); }
[data-theme="dark"] .chart-grid { stroke: var(--border-default); }
[data-theme="dark"] .chart-label { fill: var(--text-secondary); }
```
## Dark Mode Color Rules
| Element | Light Mode | Dark Mode | Principle |
|---------|-----------|-----------|-----------|
| Page background | White (#fff) | Near-black (#09090b) | Don't use pure black — it's harsh |
| Card background | White (#fff) | Slightly lighter (#18181b) | Elevated surfaces get lighter in dark mode |
| Modal background | White (#fff) | Lighter still (#1c1c1e) | Higher elevation = lighter |
| Text primary | Near-black (#18181b) | Near-white (#fafafa) | Don't use pure white — it's harsh |
| Text secondary | Medium gray (#52525b) | Medium gray (#a1a1aa) | Both need to pass contrast |
| Borders | Light gray (#e4e4e7) | Dark gray (#27272a) | Visible but subtle |
| Primary color | 600 shade | 400-500 shade | Lighter on dark backgrounds |
| Shadows | Subtle (5-10% opacity) | Heavier (20-40% opacity) | Need more punch on dark |
### The Elevation Principle
In dark mode, higher elements are LIGHTER (opposite of light mode where shadows create depth):
```
Background: #09090b (darkest)
Card: #18181b (slightly lighter)
Modal: #1c1c1e (lighter still)
Dropdown: #27272a (even lighter)
Tooltip: #3f3f46 (lightest)
```
## Testing Dark Mode
### Visual Scan Checklist
- [ ] Every page background is dark (no white flashes)
- [ ] All text is readable (contrast check)
- [ ] All inputs have visible borders and text
- [ ] All buttons are visible and have hover states
- [ ] Cards and containers are distinguishable from the background
- [ ] Modals have dark backgrounds (not white on dark page)
- [ ] Images are not blindingly bright
- [ ] Charts and graphs are readable
- [ ] Focus rings are visible on dark backgrounds
- [ ] Scrollbars are styled for dark mode (or use overlay scrollbars)
- [ ] Third-party embeds don't create white rectangles
### Flash Prevention
Prevent the "white flash" on page load before theme is applied:
```html
<!-- Add to <head> before any CSS -->
<script>
(function() {
var theme = localStorage.getItem('theme');
if (!theme) {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
```
## Anti-Patterns
- Don't use pure black (#000000) for backgrounds. It's too harsh. Use near-black (#09090b).
- Don't use pure white (#ffffff) for text on dark. Use near-white (#fafafa).
- Don't invert colors programmatically. `filter: invert(1)` creates garbage.
- Don't forget about third-party widgets (auth forms, chat widgets, embeds).
- Don't ship partial dark mode. If half the components are dark and half are white, it looks
worse than no dark mode at all.
- Don't use the same shadow values in both modes. Dark mode needs heavier shadows.