webdevpuneet.com

CSS light-dark() Theme Demo — Free Native Auto Light/Dark Card

CSS light-dark() Theme Demo · Visualizers · Plain HTML, CSS & JS · Live preview

CategoryVisualizers

Copy & paste

Source Code

<div class="demo-wrap">
  <p class="demo-note">No JavaScript, no class toggling, no data attributes. Switch your OS or browser color scheme (light/dark) and this whole card re-themes itself automatically via the CSS <code>light-dark()</code> function.</p>

  <article class="theme-card">
    <header class="card-head">
      <span class="dot-brand" aria-hidden="true"></span>
      <h2>Weekly Digest</h2>
    </header>
    <p class="card-body">Five stories worth your five minutes, picked without an algorithm. Read at your own pace, in whichever color scheme your system already prefers &mdash; this card never asks.</p>
    <div class="tag-row">
      <span class="tag">Design</span>
      <span class="tag">Reading</span>
      <span class="tag">No JS</span>
    </div>
    <button class="card-btn" type="button">Open digest</button>
  </article>

  <div class="fallback-note">
    <strong>No JS fallback:</strong> browsers without <code>light-dark()</code> support fall back to the light palette defined directly on each custom property, via an <code>@supports not</code> block &mdash; the card never looks broken, it just stops auto-switching.
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Zero JavaScript
Theming is entirely CSS — no toggle script, no class, no attribute.
Single source per token
Each color defined once as a light/dark pair, not two separate blocks.
Live OS-driven switching
Colors update instantly when the system preference changes.
Required color-scheme declaration
:root { color-scheme: light dark } tells the browser both palettes exist.
Honest @supports fallback
Unsupporting browsers get a safe, fixed light theme, never broken colors.
No drift risk
Light and dark values live in the same line, so they can't fall out of sync.
Works with existing components
Any element using these custom properties gets theming for free.
Complements JS toggles
Layer a real dark-mode switch on top by setting color-scheme explicitly.

About this UI Snippet

CSS light-dark() Theme Demo — Zero-JS Automatic Theming

Screenshot of the CSS light-dark() Theme Demo snippet rendered live

Most dark-mode implementations on the web involve either a media query duplicating every color variable under @media (prefers-color-scheme: dark), or a JavaScript-driven class/data-attribute toggle like the dark mode toggle snippet. The CSS light-dark() function, now shipping in current Chrome, Edge, Safari, and Firefox, offers a third option: define each color exactly once, as a light/dark pair, in a single custom property — no duplicated blocks, no JavaScript, no attribute to flip.

How light-dark() actually works

light-dark(lightValue, darkValue) is a CSS function that resolves to whichever of its two arguments matches the current color scheme. It requires color-scheme: light dark to be set on the element (usually :root) so the browser knows both palettes are genuinely supported and which preference to honor — without that declaration, light-dark() has no signal to resolve against. Every themed value in this card — background, border, text, accent — is written once as --bg: light-dark(#f5f4ef, #101218);, and the browser picks the right half automatically whenever the OS or browser-level color-scheme preference changes, live, with no reload and no script.

Why this beats duplicated media queries

The older approach of writing a full custom-property block once at :root and a second, overriding block inside @media (prefers-color-scheme: dark) { :root { ... } } works, but doubles the list of variables to maintain and makes it easy for the two blocks to drift out of sync as a design evolves. light-dark() keeps the light and dark value for a given token physically adjacent in the same declaration, which is both less code and harder to accidentally desync.

Where light-dark() doesn't replace JS toggles

This function only ever tracks the OS/browser-level prefers-color-scheme signal — it has no concept of a user-facing in-app toggle that overrides the system setting. If your product needs an explicit "force dark regardless of OS" switch, you still want the dark mode toggle or color mode toggle pattern, which can layer on top by setting color-scheme explicitly per user choice rather than only inheriting the OS default.

Honest support notes

light-dark() is genuinely new: it shipped in Chrome 123, Safari 17.5, and Firefox 120, all in 2024, so it's safe in any current evergreen browser as of 2026 but will fail silently-ish in older or less common engines. This snippet includes an @supports not (color: light-dark(#000, #fff)) block that redefines the same custom properties to a fixed light palette, so an unsupporting browser still renders a correct, readable card — it just won't auto-switch with the system preference there. Pair this with CSS container query units or the :has() selector playground for more of the modern, JS-free CSS toolkit.

Step by step

How to Use

  1. 1
    Toggle your OS appearance settingSwitch your operating system between light and dark mode.
  2. 2
    Watch the card re-theme with zero reloadEvery color updates live — no page refresh, no script running.
  3. 3
    Inspect the CSS custom propertiesEach token like --bg is defined once as light-dark(lightValue, darkValue).
  4. 4
    Check color-scheme: light dark on :rootThis declaration is required for light-dark() to resolve correctly.
  5. 5
    Look at the @supports fallbackUnsupporting browsers get a fixed light palette instead of broken colors.
  6. 6
    Compare to a JS toggleThis approach has no manual override — pair with a real toggle if you need one.

Real-world uses

Common Use Cases

Zero-JavaScript theming for content pages
Theme a marketing or content page automatically from the operating system, with each colour defined once as a light and dark pair.
Design system tokens
Define brand colours once per token with light-dark(), avoiding a duplicate set of variables inside a media query.
Manual toggle pairing
Pair with a dark mode toggle when users should override the system setting, remembering color-scheme: light dark is required.
Smaller stylesheets
Eliminate duplicated prefers-color-scheme blocks to get a smaller stylesheet, with colours updating instantly when the system preference changes.
Static sites and modern CSS showcases
Get correct theming even when JavaScript is blocked, or present it beside the CSS has selector playground.
Related: 500 Internal Server Error Page
See the 500 Internal Server Error Page for a related layouts pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

It needs color-scheme: light dark set on the element (typically :root or html) so the browser knows the page genuinely supports both palettes and which preference to check. Without that declaration, light-dark() has no reliable signal to resolve against and will not switch correctly.

On its own, light-dark() only tracks the OS/browser-level prefers-color-scheme signal. It has no built-in concept of a manual, user-facing in-app override. If you want users to force a theme regardless of their OS setting, you still need a JS-driven approach like the dark mode toggle or color mode toggle snippets, which can set color-scheme explicitly per user choice.

It shipped in Chrome 123, Safari 17.5, and Firefox 120, all landing in 2024, so it is safe across all current evergreen browsers as of 2026. Older browser versions and some less common engines do not support it, which is why this snippet includes an @supports not (color: light-dark(#000, #fff)) fallback block.

The @supports not (color: light-dark(#000, #fff)) block redefines the same custom properties to a fixed, readable light palette, so the card renders correctly and legibly — it simply won't auto-switch with the system color-scheme preference in that browser, rather than showing broken or transparent colors.

It is more concise for token definitions: instead of a full :root block plus a duplicated @media (prefers-color-scheme: dark) { :root { ... } } block, each variable is written once with both values adjacent, reducing duplication and the risk of the two palettes drifting out of sync as the design changes. A media query approach still works and is more broadly supported, so it remains a reasonable choice for wider legacy support.