miinideckmiinideck
PricingUse casesBlog
Sign in
Prompting AI

Prompt AI for dark mode and theming in one file (2026)

AI gives you a dark mode that flashes white, forgets the toggle, and hardcodes one shade of gray everywhere. The token-first prompt that makes theming survive in a single self-contained HTML file.

By miinideck·August 28, 2026·8 min read
TL;DR
  • AI hands you a dark mode that almost works: it flashes white on load, forgets the toggle on refresh, ignores the visitor's system setting, and hardcodes one shade of gray you can never cleanly change. Each is a different prompt-level miss, not a tool failure.
  • The fix is a token-first ask — every color routes through a CSS variable, the two palettes live in two small blocks, and a tiny script in the <head> resolves the theme before the page paints.
  • One combined prompt covers all four: token layer, precedence rule (system default, saved choice wins), no-flash head script, and a self-audit pass. It carries across Claude, ChatGPT, v0, Lovable, Cursor, and Bolt.

You ask the AI for a dark mode toggle. It adds one. You click it — the page goes dark, looks great. Then you refresh, and it's light again. You open the file on a phone that's already in system dark mode, and it loads light, flashes, then snaps to dark a beat later. And when you decide the dark background is a touch too black, you go to change it and find #0a0a0a typed out in eleven different places.

None of this is the tool being bad at CSS. It's the prompt being silent on the four things that make a theme actually hold together. Dark mode is deceptively shallow as a request and surprisingly layered as an implementation — and in a single self-contained HTML file, with no framework and no build step to lean on, every layer has to be asked for out loud.

The four things that go wrong

Pull a default AI dark-mode implementation apart and the same four gaps show up:

  1. Scattered colors. The model writes raw hex values inline on each element. The toggle works by swapping a dark class that re-declares every color again. Editing the palette means hunting the same value through hundreds of lines.
  2. The white flash. The theme is applied by JavaScript that runs after the page has already painted. The browser shows light, then repaints dark. On a slow connection it's a visible blink (the technical name is FOUC — flash of unstyled content).
  3. The dropped preference. The toggle changes the look but writes nothing down. Refresh, and the page is back to its default, your choice forgotten.
  4. Ignoring the system. A visitor whose whole device is in dark mode still gets a light page on first load, because the file never checked prefers-color-scheme.

Each maps to one clause you can add to the prompt. Here's the layer that fixes the first one, because everything else sits on top of it.

Start with a token layer, not colors

The single instruction that changes the most: ask for CSS custom properties — design tokens — instead of literal colors.

Don't write color values inline. Define a token layer: every color in the file references a CSS variable like var(--bg), var(--surface), var(--text), var(--text-muted), var(--border), var(--accent). Put the actual hex values in two blocks only — :root for the light palette and [data-theme="dark"] for the dark palette. Switching themes should mean nothing more than flipping the data-theme attribute on the <html> element.

Now the whole palette lives in roughly a dozen lines, twice. Want a warmer dark? Edit --bg in one place. Want to hand the file to someone to rebrand? They touch two small blocks, not the markup. The toggle stops being a class that re-declares everything and becomes a single attribute flip the variables respond to automatically.

This is also the layer that makes the next three fixes small, because once theme is "the value of one attribute on <html>," resolving it early and saving it are each just a couple of lines.

Kill the flash in the head

The white blink is a timing problem. The browser paints with whatever's in the document before your script runs; if the theme-setting script is at the bottom of the body, the paint already happened in light.

Before the page renders, resolve the theme. Put a small inline <script> as the first thing inside <head>, before any styles or content: read the saved choice from localStorage, fall back to prefers-color-scheme, and set the data-theme attribute on document.documentElement synchronously. This runs before first paint so there is no flash.

That ordering is the whole trick — the script is tiny, but it has to be first, above the CSS, above the body. Most AI tools put all the JavaScript together at the end by default, which is exactly where it can't help. Naming "first thing in the head, before first paint" moves it.

Theming bugs hide on your own machine — your system's already in the right mode, the cache is warm. Drop the file at a private link and open it cold on your phone to see whether the flash and the system-default actually behave. No card, no account, 7-day self-destruct.

Drop the file, test on a real network

Name the precedence so the choice sticks

The dropped-preference and ignored-system bugs are the same unanswered question: when the visitor's saved choice and their system setting disagree, which one wins? The model can't infer that from "add a dark mode toggle," so it picks one path and quietly drops the other — usually it either always follows the system (your toggle does nothing on reload) or always follows the toggle (a dark-mode visitor still gets a light first load). Spell out the order and both bugs close at once.

Theme precedence: on first visit, default to the visitor's system setting via prefers-color-scheme. When the visitor clicks the toggle, that explicit choice overrides the system setting and is written to localStorage. On every later visit, the saved choice wins over the system setting. So: saved choice beats system default; the toggle writes the saved choice.

Now the toggle does two jobs — flip the attribute and record the decision — and the head script reads that record before paint. System preference for the newcomer, remembered choice for the returner. That's the behavior people expect without being able to name it, and it falls out of one ordered sentence.

One prompt that covers all four

Stacked together, for a one-shot generation:

Build [the thing] as a single self-contained HTML file with a light/dark theme:

  1. Tokens: all colors reference CSS variables (--bg, --surface, --text, --text-muted, --border, --accent). Hex values live in two blocks only: :root (light) and [data-theme="dark"] (dark). No inline hex anywhere else.
  2. No flash: an inline <script> as the first child of <head> reads localStorage then prefers-color-scheme and sets data-theme on <html> synchronously, before any paint.
  3. Precedence: system setting is the first-visit default; a toggle click overrides it and saves to localStorage; the saved choice wins on return visits.
  4. Toggle: a visible control that flips data-theme and persists the choice.

Before finishing, verify: no raw hex outside the two palette blocks, the theme script is the first element in <head>, and the toggle writes to localStorage.

The closing verification clause earns its place — it's a second pass where the model catches the inline hex it scattered out of habit. If you're also fighting CDN and font externals, fold this into the broader portable self-contained HTML prompt patterns so theming and portability come out clean in the same generation.

This holds across Claude, ChatGPT canvas, v0, Lovable, Cursor, and Bolt. Each renders a slightly different toggle and palette, but the four-layer structure survives — because it describes CSS and DOM behavior, not anything tool-specific.

Why this all survives at a private link

Everything above is client-side: CSS variables, one localStorage key, a few lines of JavaScript. No server resolves themes; no database stores preferences. That's precisely why it works on a static host — the toggle is pure browser state, and localStorage is keyed to whatever domain serves the file, so each visitor's choice persists per-origin in their own browser with nothing running behind it. Whether you host the AI artifact at an unguessable link or put it behind a password, the theming behaves exactly as it did locally.

The one honest boundary: if you want a logged-in visitor's theme to follow them across different devices and browsers, that's no longer a single-file job. Syncing a preference to an account needs a server and a database — a real app platform like Vercel or Netlify, with auth and storage. For one shared HTML file, per-device client-side persistence is the right answer and the complete one. The moment you need cross-device sync is the moment you've stopped shipping a file and started running an app.

For everything short of that — a report, a deck, a one-pager, a vibe-coded prototype you want to look right in either mode the second it lands — the four-layer prompt gets you a dark mode that doesn't flash, doesn't forget, and stays one edit away from any palette you want next.

More in Prompting AI

How to prompt AI to inline images as base64 so the file is truly self-contained (2026)

A self-contained HTML file means the images travel inside it, not as links. The prompt wording that makes an AI base64-encode assets on the first pass — plus the size budget, what to inline, and what to leave alone.

September 1, 2026·7 min read

Prompt AI for a multi-page site that still ships as one bundle (2026)

A multi-page site usually means a router and a server. Prompt patterns that give you real pages — Home, Pricing, About — inside one self-contained file or one ZIP you can drop at a private link.

August 30, 2026·7 min read

Prompt AI for print- and PDF-friendly HTML (2026): the phrases that survive Ctrl+P

Most AI-built HTML looks right on screen and breaks the moment someone hits Ctrl+P. The exact prompt clauses, CSS hooks, and a ninety-second verification pass that make a page export to PDF cleanly the first time.

August 31, 2026·7 min read

Send your own private link.

miinideck turns a single HTML file into an unguessable link with optional password and expiry. Default-private, never indexed.

Try it free →See pricing
miinideck

HTML files, finally as links — for AI builders, agencies, and consultants. Default-noindex, default-private, default-yours.

Product

  • Pricing
  • Use cases
  • Try it free

Resources

  • Blog
  • Free tools
  • Featured on
  • Report abuse

Legal

  • Privacy
  • Terms
Listed onmiinideck listed on Product Huntmiinideck listed on Faziermiinideck listed on TheSaaSDirmiinideck listed on AIToolHuntmiinideck listed on LaunchNest
© 2026 miinideckMade for people who don't want their work indexed.