22 CSS Dark Mode UI
A CSS dark-mode UI is a surface whose theme system, tokens, and toggle contract are production primitives — not a colour swap. These 22 hand-coded patterns cover prefers-color-scheme, [data-theme], color-mix(), light-dark(), WCAG 2.4.13 focus rings, a System / Light / Dark switcher, and full UI surfaces — sidebars, dashboards, modals, code editors, OLED cards, layered slate. Every demo is scoped under .dmu-NN, uses pure-CSS :has(:checked) toggles that flip on any OS, guards motion behind prefers-reduced-motion, and drops into React, Vue, Svelte, or Astro without framework lock-in.
Related5 CSS Variable Dark Mode Systems10 CSS Dark Mode Toggles20 CSS Neon Effects23 Tailwind CSS Dark Mode UI
Premium design systemPartner: Untitled UI
Affiliate linkTaking this style from one component to a whole product?
You just browsed 22 patterns. Untitled UI is the Figma UI kit 380,000+ designers use, with 10,000+ components and variants in Pro, plus a matching React library on Tailwind CSS. Figma Pro is a one-time purchase from $129.
Untitled UI. We may earn a commission, at no extra cost to you.
02. Dark Mode UI with data-theme Attribute Override (opens in a new tab)
[data-theme] attribute wins over the media query, and a pure-CSS toggle — a visually hidden <input type="checkbox"> read by :has(:checked) — wins over both. Includes the nested-media flip-back rule, the one line that stops the toggle from silently doing nothing for visitors whose OS already matches your default.03. Dark Mode Token System with color-mix and CSS Custom Properties (opens in a new tab)
--gray-0 … --gray-12, --accent-500), then derive every semantic token — surface, elevated surface, hairline, ink, muted ink, accent wash — with color-mix(in oklab, …). Two cards sit on opposite surfaces using the identical token names to prove the derivation adapts instead of inverting.04. Dark Mode UI with light-dark CSS Function (opens in a new tab)
color-scheme: light dark and declare every token once as light-dark(<light>, <dark>). The function resolves against the element's effective colour scheme at compute time, so changing color-scheme anywhere re-themes the whole subtree — which makes the toggle a single property change instead of a second token block.05. Adaptive Dark Mode Sidebar Navigation (opens in a new tab)
06. Dark Mode Dashboard Card Grid (opens in a new tab)
07. Dark Mode Modal Dialog with Backdrop (opens in a new tab)
<details> disclosure supplies the open state, the <summary> doubles as the trigger and the dismiss control, and a fixed blurred backdrop sits between them. The sheet uses an elevated surface with an inner-glow hairline — the detail that separates a dark modal from a grey box — and animates in with @starting-style so the entrance costs nothing at runtime.08. Dark Mode Form with Focus Ring Contrast Audit (opens in a new tab)
outline in a per-theme focus token: bright cyan on dark, deep violet on light, each measured at 3:1 or better against the surface it lands on. An annotation ribbon lists what was tuned and the contrast value it hit.09. Three State Theme Switcher (System / Light / Dark) (opens in a new tab)
prefers-color-scheme through; Light and Dark pin explicit token sets that win over the media query. Three radios, three labels, one sliding thumb, zero JavaScript — plus a live badge that reports what the OS is currently asking for, so the difference between "System" and "Dark" is visible rather than theoretical.10. Dark Mode Code Editor Surface with Syntax Layer (opens in a new tab)
11. OLED True Black Dark Mode Card (opens in a new tab)
#000 and every layer above it climbs a short surface ramp — #0a0a0a, #141414, #1c1c1c — instead of relying on shadows that a true-black substrate swallows. The result is deeper contrast, a measurably cheaper screen and an interface that looks like it was designed for the display rather than ported to it.12. Stealth Panel Minimalist Dark UI (opens in a new tab)
13. Inkwell Dark Mode Modal Dialog (opens in a new tab)
14. Cyber Terminal Dark UI Panel (opens in a new tab)
prefers-reduced-motion.15. Mocha Warm Dark Mode Workstation (opens in a new tab)
16. Forest Dim Dark Mode Reading Surface (opens in a new tab)
17. Slate Stack Layered Dark Mode Card (opens in a new tab)
18. Carbon Shelves Dark Mode File List (opens in a new tab)
color-mix() rather than two hard-coded greys, and a selected row marked by an accent edge plus a checked box — never colour alone. Row density, hover feedback and focus rings are all tuned for the case that actually happens: forty rows, scanned quickly, on a dim screen.19. Onyx Ladder Dark Mode Timeline (opens in a new tab)
20. Phosphor Glow Dark Mode Card (opens in a new tab)
box-shadow in the accent hue, layered under a tight inner highlight so the button looks lit rather than blurred. A live countdown ticks in tabular figures (fifteen lines of vanilla JS, no theming logic), and the ambient pulse behind the card stops dead under prefers-reduced-motion.21. Aurora Dim Dark Mode Landing Panel (opens in a new tab)
22. Ember Pulse Dark Mode Alert Card (opens in a new tab)
Build your own
Tweak the exact look in our visual generators — no signup, instant copy-paste.
Frequently asked questions
How do I add dark mode to a CSS project without JavaScript?
prefers-color-scheme — declare your tokens once on the root, then redeclare only the tokens inside @media (prefers-color-scheme: dark). The UI adapts to the operating system automatically with zero script. Second, [data-theme] attribute override — pair the media query with .root[data-theme="dark"] and .root[data-theme="light"] rules that WIN over the media query so a user preference can beat the OS. For an interactive toggle without any JavaScript, use a visually-hidden <input type="checkbox"> plus a <label> and read the state with :has(#your-id:checked) on the ancestor — the whole flow is pure CSS. Demos 01, 02, and 04 in this collection ship each pattern as a reference implementation. The gotcha every toggle build hits: on the visitor's OS-matching default, the :has(:checked) rule and the OS default land on the same colours, so the first click looks like it did nothing. Fix by adding a nested rule inside the @media block that restores the opposite base when :has(:checked) matches — the flip-back pattern makes the toggle work in both directions on any device.Should dark mode use pure black (#000) or dark grey backgrounds?
#000 costs less battery, gives infinite contrast and reads as intentional. Demo 11 uses this substrate with a short elevation ramp (#0a0a0a, #141414, #1c1c1c) for cards, rows and hover states, because a shadow on true black would be swallowed by the substrate — surface tokens do the depth work instead. For non-OLED displays, a very dark grey (roughly #0a0a0b to #111114) usually reads better: it softens the contrast with pure white text, and layering progressively lighter greys lets you show elevation without shadows. The right choice depends on your primary audience and device mix — dashboards used on desktop monitors typically pick dark grey; reading apps and streaming interfaces used on OLED phones and TVs pick true black. Every demo in this collection declares its ground colour as a --void or --bg token so you can swap between the two by editing one value.How do color-mix() and light-dark() reduce dark-mode boilerplate?
color-mix(in oklab, base 90%, white 10%) collapses that to one: define primitive tokens once, then compose semantic tokens as mixes relative to currentColor or a base neutral, and the mix adapts automatically to the surface it lands on. Perceptual mixing in oklab keeps mid-mixes from going muddy the way sRGB does. The newer light-dark() CSS function goes further — set color-scheme: light dark on the root and every colour can be written as light-dark(#f4f4f5, #0a0a0a). The function reads the effective color-scheme at compute time, so a single [data-theme] swap on the root cascades through every declaration with zero media queries. Baseline widely available in Chromium since May 2024, Safari since March 2024, Firefox 120+ — production-safe for a codebase with a modern-browser floor. Demo 03 shows the color-mix primer, demo 04 shows light-dark(), and both wrap their bleeding-edge features in @supports gates with sRGB fallbacks declared first.How do I meet WCAG 2.2 AA for a dark-mode focus ring?
#0a0a0a. Two rules keep this reliable. First, calibrate a distinct --focus-ring token per theme rather than reusing the primary accent — bright cyan (#22d3ee) works on most dark surfaces because oklch(83% 0.13 200) sits high in lightness. Second, use outline: 2px solid var(--focus-ring); outline-offset: 2px; rather than a box-shadow ring — outlines respect outline-offset cleanly and never get clipped by overflow: hidden. Demo 08 is the reference implementation with an inline audit ribbon calling out the contrast values for every interactive control on the surface. Pair with SC 1.4.11 (Non-text Contrast, 3:1) for the borders around inputs and buttons in their idle state, and SC 1.4.3 (Contrast Minimum, 4.5:1) for body text against the dark background.How should a three-state theme switcher (System / Light / Dark) work in pure CSS?
name attribute, one label per state, and :has() selectors on the surface for the two explicit states — the System option is the DEFAULT and needs no override rule. The cascade goes: @media (prefers-color-scheme: dark) supplies the base when nothing is checked (System = follow OS); :has(#light:checked) overrides both media queries with explicit light tokens; :has(#dark:checked) overrides with explicit dark tokens. The subtle bug every implementation hits: inside @media (prefers-color-scheme: dark), add a :has(#light:checked) restore-light rule that WINS over the media query, and inside @media (prefers-color-scheme: light) add a :has(#dark:checked) restore-dark rule. Without those two nested rules, a visitor on the matching OS gets stuck on their default when they select the opposite. Demo 09 ships the reference wiring with an annotation ribbon labelling each selector and its role in the cascade. Bonus: pair the switcher with a JavaScript one-liner that syncs <meta name="theme-color"> so the browser chrome (address bar on mobile, title bar on macOS) tracks the theme.How do I stop the flash of light content when the page loads in dark mode?
[data-theme="dark"], and the whole page flips. Three complementary fixes. First, put a tiny synchronous script BEFORE the first paint that reads localStorage.getItem('theme') and stamps document.documentElement.setAttribute('data-theme', value) — put it in the <head> above every stylesheet link, keep it under 200 bytes, and skip any imports. Second, add <meta name="color-scheme" content="light dark"> in the head — the browser paints the default background-color and scrollbars in dark grey before your CSS loads, which eliminates the white flash even without JavaScript. Third, ensure your @media (prefers-color-scheme: dark) block sets color-scheme: dark on the root so native form controls, scrollbars and text-selection adopt dark styling immediately. Combining all three eliminates flash on cold loads, warm loads, view transitions and Safari's bfcache restores.Which dark-mode design surfaces are worth building as reusable primitives?
--surface-0 through --surface-3 with 8-15 unit lightness steps in oklab — replaces box-shadow depth on true-black substrates and reads as physical layering. Second, a focus-ring token distinct from the primary accent, calibrated for 3:1 contrast against the dark surface (WCAG 2.4.13). Third, an --accent-glow token that pairs with --accent for emissive CTAs (demo 20's phosphor glow, demo 22's ember pulse) so glow intensity flips off in light mode. Fourth, a semantic status palette (--ok, --warn, --err) that reads correctly on both grounds — saturated mid-tones that pass on white often fail on #0a0a0a and need a lightness lift. Fifth, a --divider-strong and --divider-subtle pair so hairlines stay visible on the dark substrate without ringing every card. Sixth, tabular-nums typography via font-variant-numeric: tabular-nums for any numeric display — column-aligned figures read as data on dark surfaces where a lighter-weight body copy would blur. Demos 03, 06, 17, 18 and 19 each show one or more of these primitives in production context.How portable are these dark-mode patterns across Tailwind, React, Vue, Svelte, and Astro?
.dmu-NN root so it drops into any framework without leaking. Tailwind v3 users pair the demo with the darkMode: 'class' config so dark: variants respect the same [data-theme] attribute the demo listens to; Tailwind v4 uses @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)) in your CSS-first config. React and Next.js apps typically ship the theme decision as a Context provider that writes data-theme on document.documentElement — the demo CSS reads the attribute regardless of who sets it. Vue 3 users bind :data-theme="theme" on the layout root and useDark() from VueUse writes it. Svelte does it with bind:data-theme on the <svelte:element> root. Astro islands (client:visible) work with the pure-CSS demos as-is; for the interactive three-state switcher, hydrate a small island around demo 09's markup. SolidJS, Qwik, and Angular signals all map to the same [data-theme] attribute pattern. The light-dark() demo (04) is the most future-proof — with color-scheme: light dark on the root, a single attribute swap cascades through every colour declaration with zero framework glue.Related collections
20 CSS 3D Effects
20 hand-coded CSS 3D patterns covering the transform pipeline reference build, the perspective property versus the perspective function, perspective-origin as the camera position, why transform function order changes the result, backface-visibility on two sided elements, what silently flattens preserve-3d, building a cube from six faces, transform-origin in three dimensions, translateZ versus scale for depth, z-fighting on coplanar surfaces, rotate3d and the transform matrix, plus ring layouts, stacked z-planes, pointer tilt with parallax, a hinged page-turn flipbook, a drag-rotate cube navigator, an extruded synth panel, perspective floor geometry, nested 3D contexts, and helix distribution. 14 demos are pure CSS; 6 use vanilla JavaScript.
22 CSS Brutalism
22 hand-coded brutalist and neubrutalist CSS designs covering the full spectrum of the aesthetic — from the anti-design brutalism of 2014-2018 (raw system fonts, zero border radius, monochrome density, visible structure) through to the neubrutalism of 2021-present (bright pastel fills, thick black borders, hard offset shadows with zero blur, rounded blocks). The neubrutalist half ships SaaS pricing cards with sticker badges, a pastel admin dashboard, signup forms, rotated feature cards, an offset-shadow navbar, a playful button set, a native dialog modal, a testimonial wall, blog cards, chunky toggles, a landing hero, an image gallery, stats panels and a rounded footer. The brutalist half covers product grids, raw form controls, a monochrome data dashboard, a portfolio index, indie SaaS pricing, Windows 95 desktop chrome, editorial long-read and a streetwear catalog. Every design is scoped under a .brt-NN prefix for no-collision pasting, guards prefers-reduced-motion, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.
20 CSS Claymorphism
20 hand-coded CSS claymorphism designs covering the three-layer shadow recipe that defines the style, inner highlight and inner shadow layering, the border radius scale that makes shapes read as puffy rather than merely rounded, claymorphism compared side by side with neumorphism, a clay and glassmorphism hybrid, single light-source discipline across a layout, depth tiers for visual hierarchy, a Tailwind implementation with both arbitrary values and theme tokens, squishy button press states, toggles and checkboxes, inset input wells and search bars, badge and pill systems, cards, popover-based modals, pricing tables, mobile tab bars, app icon grids, toast and alert bubbles, dark mode shadow recalibration, and the paint cost of animating shadows. 19 demos are pure CSS; 1 uses vanilla JavaScript.





















