Building Block

Dark Mode & Theme Toggle

System-aware dark mode with CSS custom properties, animated sun/moon toggle buttons, and multi-theme colour pickers — flicker-free patterns that Claude deploys in a single conversation. No framework, no flash of wrong theme.

Dark mode & theme patterns

Three patterns covering system-preference-aware dark mode, an animated toggle button UI, and a full multi-theme colour system. Claude generates CSS custom properties, vanilla JS, and optional PHP session persistence — matched to your existing theme and deployed live.

Pattern 1 — System-Aware Dark Mode with CSS Variables

A complete dark mode implementation using CSS custom properties. The site detects the visitor’s OS preference via prefers-color-scheme: dark and applies the dark theme automatically. If the visitor manually toggles, their preference is stored in localStorage and overrides the system default on every subsequent visit — with no flash of the wrong theme.

CSS Variables Vanilla JS LocalStorage No Flicker prefers-color-scheme
  • Light theme: CSS custom properties defined in :root (--bg, --surface, --text, --text-muted, --border, --primary)
  • Dark theme: same variables overridden in [data-theme="dark"] selector on <html>; darker --bg and --surface values, lightened text for contrast
  • System auto-apply: @media (prefers-color-scheme: dark) sets html[data-theme="auto"] to dark values — no JavaScript needed for the default case
  • Flash prevention: an inline <script> in <head> (before any CSS) reads localStorage.getItem('theme') and sets document.documentElement.dataset.theme synchronously — the browser never paints the wrong theme
  • Smooth transitions: :root { transition: background-color 0.25s ease, color 0.25s ease; } only — not applied to * which would cause expensive layout recalculation on every element
  • Three states: "light" (forced light regardless of OS), "dark" (forced dark), "auto" (follows OS) — all stored in localStorage
  • prefers-reduced-motion: transitions disabled entirely when motion preference is set
Prompt to use with Claude

Add dark mode to my site using CSS custom properties. Light theme: bg [light hex], surface [card hex], text [text hex]. Dark theme: bg [dark hex], surface [dark card hex], text [light hex]. Auto-detect OS preference via prefers-color-scheme. Store manual override in localStorage. Prevent flash of wrong theme with an inline head script. Smooth 0.25s transitions on :root only. CSS variables in theme.css, JS in main.js.

Pattern 2 — Animated Sun / Moon Toggle Button

A polished toggle button that switches between sun ☀ and moon 🌙 icons with a smooth CSS animation. The button can be placed in the navbar, as a fixed floating element, or inline anywhere on the page. ARIA attributes update to reflect the current state, and the button label describes the action (“Switch to dark mode”) rather than the current state.

CSS Animation ARIA Navbar Accessible No Images
  • Button uses CSS-only icon morphing: sun rays drawn as ::before/::after pseudo-elements; moon shape achieved via clip-path: circle() offset — no image files required
  • Toggle animation: icon rotates 180° and fades during switch via @keyframes; prefers-reduced-motion replaces rotate with a simple opacity crossfade
  • aria-label attribute updated by JS to “Switch to dark mode” or “Switch to light mode” depending on active state
  • aria-pressed set to "true" when dark is active; screen readers announce “Toggle dark mode, pressed”
  • Position variants (all configurable via a single CSS class): inline in <nav>, fixed bottom-right floating circle, or absolute top-right of hero section
  • Optional third state “Auto”: button cycles Light → Dark → Auto with a computer monitor 🖥 icon for the auto state and tooltip “Using system preference”
  • Ripple effect on click: CSS @keyframes radial expand from click point, brand colour at 20% opacity, 400ms duration
Prompt to use with Claude

Add a dark mode toggle button to my navbar. Sun/moon icons using CSS pseudo-elements (no images). Smooth 180deg rotation animation on toggle. Three states: Light, Dark, Auto (follows OS). aria-label updates to describe the action. Fixed position: top-right of navbar, same row as existing nav links. Ripple effect on click. CSS in theme.css, JS in main.js. Hook into the existing dark mode CSS variable system.

Pattern 3 — Multi-Theme Colour System

Beyond dark and light — a full theme picker that lets visitors choose from multiple named colour palettes (e.g. Default, Ocean, Forest, High Contrast). Each theme is a set of CSS custom property overrides. The active theme is stored in localStorage for anonymous users and in a MySQL user_preferences table for logged-in users.

CSS Variables PHP MySQL prefers-contrast WCAG
  • Each theme defined as a [data-theme="ocean"] selector block with full CSS variable overrides; themes are additive — only the changed variables need to be declared
  • Theme picker UI: floating drawer triggered by a palette icon button; colour swatches rendered from CSS variables; active theme gets a checkmark badge
  • Themes included: Default (brand colours), Dark (inverted), Ocean (teal/blue), Forest (green/earth), High Contrast (WCAG AAA black/white)
  • prefers-contrast: high media query auto-applies the High Contrast theme if no manual override is set — no JavaScript needed
  • Anonymous persistence: localStorage.setItem('theme', 'ocean'); applied on page load by inline <head> script before CSS renders (no flash)
  • Logged-in persistence: PHP PATCH /api/preferences endpoint writes to MySQL user_preferences (user_id, theme); preference loaded from DB on login and injected into session
  • Admin configurable: themes defined as a PHP array in themes.php — adding a new theme requires only a new array entry and a matching CSS block; no JS changes needed
Prompt to use with Claude

Build a multi-theme colour picker for my site. Themes: Default [my brand hex], Dark [dark hex], Ocean [teal], Forest [green], High Contrast [WCAG AAA]. Each theme as a [data-theme] CSS variable block. Theme picker: floating drawer with colour swatches, triggered by a palette icon in the navbar. Persist selection in localStorage for guests; in MySQL user_preferences for logged-in users. Auto-apply High Contrast when prefers-contrast: high. No flash on load. CSS in theme.css, JS in main.js.

Every dark mode pattern includes

No flash of wrong theme

An inline <script> in <head> reads localStorage before any CSS renders, setting the correct data-theme attribute synchronously. Visitors never see a white flash on a dark-mode site.

Matched to your theme

Claude reads your existing CSS variable names and writes the dark overrides in the same naming convention — no variables are renamed or duplicated, and your existing components get dark mode for free.

Accessible by default

Dark palettes are contrast-checked against WCAG AA (4.5:1 for body text, 3:1 for large text). The High Contrast theme targets WCAG AAA. prefers-contrast: high is handled automatically.

Iterable in seconds

Adjust a dark background shade, add a new theme, change the toggle button position. Describe the change to Claude and it’s updated on your live site within seconds.

Pair with

Dark mode and theme systems work across every component on your site — pair with these blocks for fully themed pages.

Navigation & Menus

Sticky navbars that carry the dark mode toggle button — the most natural place to surface the theme control for visitors.

View patterns

JS Animations

Scroll reveal and GSAP timeline animations that respect prefers-reduced-motion — the same accessibility principle applied to motion.

View patterns

Modals & Overlays

Modal dialogs and cookie consent banners that inherit your dark/light theme automatically via CSS custom properties — no extra dark-mode styles needed.

View patterns

Deploy dark mode today

From £6.99/month. First month free — no credit card required.