Claude Code Dark Mode Implementation Guide
Build dark mode with CSS variables, prefers-color-scheme, localStorage, hydration safety, and a11y checks.
Start with the Product Risk
Implementing dark mode with Claude Code is not about asking the agent to “make it pretty.” The practical goal is to improve a real interface without breaking conversion, readability, accessibility, or mobile layout. A beginner can get a demo quickly, but production quality depends on boring details: empty states, keyboard focus, loading states, reduced motion, stale data, dark backgrounds, and the parts of the page that make money. This article walks through introducing dark mode as a small, safe change and then growing it into something that does not break later.
Read this together with claude code design system, claude code accessibility, claude code code review. Keep the official references open: Claude Code docs, MDN prefers-color-scheme, MDN color-scheme, WCAG contrast. The useful pattern is to ask Claude Code for a constrained implementation, then ask for a second review pass before merging.
Prompt Claude Code with Boundaries
Implement this improvement with the smallest safe change.
Respect existing components, tokens, and routing.
Cover use case behavior, pitfall risks, accessibility, mobile layout, and failure states.
Return copy-paste-ready code plus a review checklist.
This prompt keeps the agent away from random redesign. It tells Claude Code what to optimize, what not to touch, and what evidence should come back with the patch.
Use Case Checklist
- Marketing and article pages where the reader must notice the next step without feeling pushed.
- SaaS dashboards where status, loading, empty data, and errors must be understood quickly.
- Checkout, signup, and consultation flows where visual polish must never hide the primary action.
- Team review workflows where Claude Code should produce code and a checklist that another person can verify.
Implementation Example
:root {
color-scheme: light;
--color-page: #ffffff;
--color-surface: #f8fafc;
--color-text: #0f172a;
--color-muted: #475569;
--color-border: #dbe3ef;
--color-link: #2563eb;
--color-focus: #f59e0b;
}
[data-theme="dark"] {
color-scheme: dark;
--color-page: #0b1120;
--color-surface: #111827;
--color-text: #f8fafc;
--color-muted: #cbd5e1;
--color-border: #334155;
--color-link: #93c5fd;
--color-focus: #fbbf24;
}
body {
background: var(--color-page);
color: var(--color-text);
}
:focus-visible {
outline: 3px solid var(--color-focus);
outline-offset: 3px;
}
const storageKey = "theme";
const root = document.documentElement;
const stored = localStorage.getItem(storageKey);
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const theme = stored === "light" || stored === "dark" ? stored : prefersDark ? "dark" : "light";
root.dataset.theme = theme;
type Theme = "light" | "dark" | "system";
export function ThemeToggle({ value, onChange }: { value: Theme; onChange: (theme: Theme) => void }) {
return (
<fieldset aria-label="Theme">
{(["light", "dark", "system"] as const).map((theme) => (
<button
key={theme}
type="button"
aria-pressed={value === theme}
onClick={() => onChange(theme)}
>
{theme}
</button>
))}
</fieldset>
);
}
Prevent the Theme Flash (FOUC)
The first pitfall you hit with dark mode is the “flash” where a bright screen appears for a split second before turning dark. In English it is called FOUC (Flash of Unstyled Content) or a theme flash. The cause is that you switch the theme with JavaScript only after the HTML has already rendered. If you wait for CSS or React to load before applying data-theme, the initial colors (usually the light ones) show during that wait.
The fix is to run a very small script that decides the theme synchronously, before the body (inside <head>). Look only at the saved preference and the device setting, and lock in data-theme before the screen is painted, like this:
<script>
(function () {
var stored = localStorage.getItem("theme");
var prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
var theme = stored === "light" || stored === "dark" ? stored : prefersDark ? "dark" : "light";
document.documentElement.dataset.theme = theme;
})();
</script>
The key is to keep this script tiny and let it run before the body is rendered. If you put heavy work or React initialization in here, the flash comes back. In Astro or Next.js, treat just this small script specially so you can settle the theme without waiting for framework hydration (the process where the browser attaches behavior to server-rendered HTML). Because the server cannot know the device setting, an easy flow is to respect the device preference on the first visit, then save the user’s choice to localStorage and prefer it from the next visit onward.
Make Contrast, Images, and Shadows Readable in the Dark
Inverting the colors does not finish dark mode. Contrast that was plenty on a light screen can fall short on a dark background. Check the contrast ratio between body text and background against the WCAG target of 4.5:1 or higher. Faint gray supporting text, placeholders, and disabled buttons in particular tend to become unreadable on a dark background.
Images and shadows also need adjustment. Logos and icons built assuming white lose their edges on a dark background. For transparent PNGs, lay down a background color or prepare a dark variant. Shadows that look effective as dark gray on a light background are nearly invisible on a dark one. Boundaries that relied on shadows should be re-expressed with a border such as --color-border, so the structure reads in either theme.
| Element | Light assumption | Dark adjustment |
|---|---|---|
| Supporting text | Faint gray is enough | Raise lightness to secure contrast |
| Logos and icons | White background assumed | Add a background or swap for a dark variant |
| Shadows | Dark gray expresses boundaries | Re-express with a border |
| Status colors | Vivid green and red | Lower saturation and add an icon |
Finally, revisit charts, diagrams, and status colors (green for success, red for warning, and so on). Highly saturated colors glare and become hard to read on a dark background, so prepare slightly calmer versions for dark mode. Do not convey state with color alone; adding icons and labels gets the message across to readers in low-contrast environments or with color vision differences. When you have Claude Code review the work, have it specifically hunt for “spots where contrast drops on a dark background,” “images that assume white,” and “boundaries expressed with shadows,” and you will miss fewer issues.
Pitfall Checklist
- Do not optimize for a screenshot only. Test the actual user path from landing page to CTA.
- Do not depend on color alone. Keep labels, text alternatives, focus states, and keyboard behavior.
- Do not introduce a heavy dependency before checking whether CSS and a small helper are enough.
- Do not trust generated code until it has been tested with empty data, long labels, mobile width, and reloads.
- Do not let Claude Code rewrite unrelated components. Ask for a file-by-file explanation of the diff.
Review and Verification
Ask Claude Code for a second pass that only reviews the change. The review should list changed files, risks, deleted assumptions, browser checks, and manual checks. Then open the page at 375px width and verify there is no horizontal overflow, the code block scrolls normally, the CTA is still visible, and assistive text is still present.
Monetization Angle
This topic is useful because it affects revenue surfaces: ads, product cards, consultation links, pricing tables, and lead forms. If your team wants a guided rollout using your own repository, the Claude Code training and consultation page explains how to turn this into a repeatable workflow.
Extra Review Notes
- Compare before and after screenshots and check CTA, ads, article text, forms, and code blocks.
- Ask Claude Code what can be removed, which names do not match the project, and which assumptions are risky.
- Before deployment, verify mobile, desktop, keyboard navigation, slow network, empty data, and reload behavior.
Hands-on Verification Note
I tested this pattern as a small, reviewable change: one implementation pass, one review pass, and one mobile browser pass. The result was easier to review than a large redesign prompt, and the checklist caught layout and accessibility issues before deployment.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Permission Safety Ladder: Expand Access Without Losing Control
A beginner-friendly ladder for moving Claude Code from read-only to limited edits, proof commands, and deploy checks.
Claude Code Small PR Proof Pack: Make Tiny Changes Reviewable
A practical proof pack for Claude Code PRs: diff, checks, public URL, CTA path, and rollback note.
Claude Code Review Gate Before Commit: Diff, Tests, Public URL, and CTA Checks
A commit-time review gate for Claude Code work: diff scope, build, public URL, revenue CTA links, missing tests, and unrelated files.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.