22 CSS Dark Mode UI01 / 22

Pure CSSMIT licensed

Dark Mode UI with prefers-color-scheme Root Strategy

The baseline pattern every other dark-mode technique builds on: declare your token block once on the component root, then let one media query re-declare the same token names for dark. No toggle, no attribute, no script — the UI simply obeys the operating system. Three panels render identical markup: a forced-light reference, a forced-dark reference, and one live panel that follows your current OS preference right now.

Published

Live Demo
Try it

The code

<div class="dmu-01">
  <!-- Pair this component with, in your document head:
       <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
       <meta name="theme-color" media="(prefers-color-scheme: dark)"  content="#09090b"> -->
  <div class="dmu-01__stage">
    <header class="dmu-01__head">
      <h1 class="dmu-01__h1">Root token strategy</h1>
      <p class="dmu-01__lede">One token block, one media query. Identical markup, three renderings.</p>
    </header>

    <div class="dmu-01__grid">
      <section class="dmu-01__panel dmu-01__panel--light">
        <p class="dmu-01__badge">Forced light reference</p>
        <article class="dmu-01__card">
          <div class="dmu-01__cardTop">
            <span class="dmu-01__dot" aria-hidden="true"></span>
            <span class="dmu-01__cardKicker">Deploy</span>
          </div>
          <h2 class="dmu-01__cardTitle">edge-runtime v4.2</h2>
          <p class="dmu-01__cardBody">Promoted to production 14 minutes ago by Alex Chen.</p>
          <div class="dmu-01__cardFoot">
            <button class="dmu-01__btn" type="button">View build</button>
            <span class="dmu-01__meta">iad1</span>
          </div>
        </article>
      </section>

      <section class="dmu-01__panel dmu-01__panel--dark">
        <p class="dmu-01__badge">Forced dark reference</p>
        <article class="dmu-01__card">
          <div class="dmu-01__cardTop">
            <span class="dmu-01__dot" aria-hidden="true"></span>
            <span class="dmu-01__cardKicker">Deploy</span>
          </div>
          <h2 class="dmu-01__cardTitle">edge-runtime v4.2</h2>
          <p class="dmu-01__cardBody">Promoted to production 14 minutes ago by Alex Chen.</p>
          <div class="dmu-01__cardFoot">
            <button class="dmu-01__btn" type="button">View build</button>
            <span class="dmu-01__meta">iad1</span>
          </div>
        </article>
      </section>

      <section class="dmu-01__panel dmu-01__panel--live">
        <p class="dmu-01__badge">
          <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><rect x="2.5" y="4" width="19" height="13" rx="2"/><path d="M8.5 20.5h7"/><path d="M12 17v3.5"/></svg>
          Live &mdash; follows your OS
        </p>
        <article class="dmu-01__card">
          <div class="dmu-01__cardTop">
            <span class="dmu-01__dot" aria-hidden="true"></span>
            <span class="dmu-01__cardKicker">Deploy</span>
          </div>
          <h2 class="dmu-01__cardTitle">edge-runtime v4.2</h2>
          <p class="dmu-01__cardBody">Promoted to production 14 minutes ago by Alex Chen.</p>
          <div class="dmu-01__cardFoot">
            <button class="dmu-01__btn" type="button">View build</button>
            <span class="dmu-01__meta">iad1</span>
          </div>
        </article>
      </section>
    </div>

    <aside class="dmu-01__notes">
      <h3 class="dmu-01__notesTitle">What the media query flips</h3>
      <dl class="dmu-01__tokens">
        <div class="dmu-01__row"><dt>--bg</dt><dd><span class="dmu-01__sw" style="background:#ffffff"></span>#ffffff<span class="dmu-01__arrow" aria-hidden="true">&rarr;</span><span class="dmu-01__sw" style="background:#09090b"></span>#09090b</dd></div>
        <div class="dmu-01__row"><dt>--surface</dt><dd><span class="dmu-01__sw" style="background:#f6f6f7"></span>#f6f6f7<span class="dmu-01__arrow" aria-hidden="true">&rarr;</span><span class="dmu-01__sw" style="background:#121216"></span>#121216</dd></div>
        <div class="dmu-01__row"><dt>--ink</dt><dd><span class="dmu-01__sw" style="background:#0a0a0b"></span>#0a0a0b<span class="dmu-01__arrow" aria-hidden="true">&rarr;</span><span class="dmu-01__sw" style="background:#f4f4f5"></span>#f4f4f5</dd></div>
        <div class="dmu-01__row"><dt>--line</dt><dd><span class="dmu-01__sw" style="background:#e3e3e7"></span>#e3e3e7<span class="dmu-01__arrow" aria-hidden="true">&rarr;</span><span class="dmu-01__sw" style="background:#26262c"></span>#26262c</dd></div>
        <div class="dmu-01__row"><dt>--accent</dt><dd><span class="dmu-01__sw" style="background:#4f46e5"></span>#4f46e5<span class="dmu-01__arrow" aria-hidden="true">&rarr;</span><span class="dmu-01__sw" style="background:#8b8bff"></span>#8b8bff</dd></div>
      </dl>
      <p class="dmu-01__notesFoot">No toggle ships here on purpose: this is the pure OS-driven baseline. Layer an override on top only when a visitor needs to disagree with their system.</p>
    </aside>
  </div>
</div>
.dmu-01 {
  --bg: #ffffff;
  --surface: #f6f6f7;
  --elevated: #ffffff;
  --ink: #0a0a0b;
  --muted: #5b5f66;
  --line: #e3e3e7;
  --accent: #4f46e5;
  --accent-ink: #ffffff;
  --focus: #4f46e5;
  --dot: #16a34a;
  --shadow: rgba(9,9,11,.10);
  --sans: system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono: ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;
  color-scheme: light dark;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

@media (prefers-color-scheme: dark) {
  .dmu-01 {
    --bg: #09090b;
    --surface: #121216;
    --elevated: #17171c;
    --ink: #f4f4f5;
    --muted: #a1a1aa;
    --line: #26262c;
    --accent: #8b8bff;
    --accent-ink: #0a0a12;
    --focus: #a5b4fc;
    --dot: #4ade80;
    --shadow: rgba(0,0,0,.6);
  }
}

@supports (color: oklch(50% .1 250)) {
  .dmu-01 {
    --bg: oklch(100% 0 0);
    --surface: oklch(97% .003 285);
    --ink: oklch(14% .01 285);
    --accent: oklch(50% .21 275);
  }

  @media (prefers-color-scheme: dark) {
    .dmu-01 {
      --bg: oklch(14% .008 285);
      --surface: oklch(19% .012 285);
      --ink: oklch(96% .004 285);
      --accent: oklch(76% .13 280);
    }
  }
}

.dmu-01,
.dmu-01 *,
.dmu-01 *::before,
.dmu-01 *::after {
  box-sizing: border-box;
}

.dmu-01__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  align-content: center;
  gap: clamp(1.5rem,3vw,2.5rem);
  padding: clamp(1.25rem,5vw,3.5rem);
  max-width: 82rem;
  margin-inline: auto;
}

.dmu-01__head {
  display: grid;
  gap: .45rem;
}

.dmu-01__h1 {
  margin: 0;
  font-size: clamp(1.35rem,3vw,1.9rem);
  font-weight: 650;
  letter-spacing: -.025em;
}

.dmu-01__lede {
  margin: 0;
  color: var(--muted);
  font-size: .95rem;
}

.dmu-01__grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit,minmax(15rem,1fr));
}

.dmu-01__panel {
  display: grid;
  gap: .75rem;
  padding: 1rem;
  border-radius: 1rem;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink);
}

.dmu-01__panel--light {
  --bg: #ffffff;
  --surface: #f6f6f7;
  --elevated: #ffffff;
  --ink: #0a0a0b;
  --muted: #5b5f66;
  --line: #e3e3e7;
  --accent: #4f46e5;
  --accent-ink: #ffffff;
  --dot: #16a34a;
  --shadow: rgba(9,9,11,.10);
}

.dmu-01__panel--dark {
  --bg: #09090b;
  --surface: #121216;
  --elevated: #17171c;
  --ink: #f4f4f5;
  --muted: #a1a1aa;
  --line: #26262c;
  --accent: #8b8bff;
  --accent-ink: #0a0a12;
  --dot: #4ade80;
  --shadow: rgba(0,0,0,.6);
}

.dmu-01__badge {
  margin: 0;
  display: flex;
  align-items: center;
  gap: .4rem;
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}

.dmu-01__badge svg {
  width: .95rem;
  height: .95rem;
}

.dmu-01__card {
  display: grid;
  gap: .6rem;
  padding: 1.05rem;
  border-radius: .85rem;
  background: var(--elevated);
  border: 1px solid var(--line);
  box-shadow: 0 1px 2px var(--shadow), 0 12px 28px -20px var(--shadow);
}

.dmu-01__cardTop {
  display: flex;
  align-items: center;
  gap: .5rem;
}

.dmu-01__dot {
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: var(--dot);
  box-shadow: 0 0 0 3px color-mix(in oklab,var(--dot) 22%,transparent);
}

.dmu-01__cardKicker {
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

.dmu-01__cardTitle {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -.02em;
  font-family: var(--mono);
}

.dmu-01__cardBody {
  margin: 0;
  font-size: .875rem;
  line-height: 1.55;
  color: var(--muted);
  text-wrap: pretty;
}

.dmu-01__cardFoot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  margin-top: .15rem;
}

.dmu-01__btn {
  min-height: 2.5rem;
  padding: 0 .95rem;
  border: 1px solid transparent;
  border-radius: .55rem;
  font: inherit;
  font-size: .85rem;
  font-weight: 550;
  cursor: pointer;
  background: var(--accent);
  color: var(--accent-ink);
  transition: filter .18s ease, translate .18s ease;
}

.dmu-01__btn:hover {
  filter: brightness(1.08);
  translate: 0 -1px;
}

.dmu-01__btn:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.dmu-01__meta {
  font-family: var(--mono);
  font-size: .75rem;
  color: var(--muted);
}

.dmu-01__notes {
  display: grid;
  gap: .85rem;
  padding: clamp(1rem,2.5vw,1.5rem);
  border-radius: 1rem;
  background: var(--surface);
  border: 1px solid var(--line);
}

.dmu-01__notesTitle {
  margin: 0;
  font-size: .95rem;
  font-weight: 600;
  letter-spacing: -.01em;
}

.dmu-01__tokens {
  margin: 0;
  display: grid;
  gap: .35rem;
}

.dmu-01__row {
  display: grid;
  grid-template-columns: minmax(6rem,9rem) 1fr;
  gap: .75rem;
  align-items: center;
  padding: .45rem .55rem;
  border-radius: .5rem;
  background: var(--bg);
  border: 1px solid var(--line);
}

.dmu-01__row dt {
  font-family: var(--mono);
  font-size: .78rem;
  color: var(--accent);
}

.dmu-01__row dd {
  margin: 0;
  display: flex;
  align-items: center;
  gap: .4rem;
  flex-wrap: wrap;
  font-family: var(--mono);
  font-size: .72rem;
  color: var(--muted);
}

.dmu-01__sw {
  width: .85rem;
  height: .85rem;
  border-radius: .25rem;
  border: 1px solid var(--line);
}

.dmu-01__arrow {
  padding-inline: .3rem;
  color: var(--ink);
}

.dmu-01__notesFoot {
  margin: 0;
  font-size: .83rem;
  line-height: 1.6;
  color: var(--muted);
  max-width: 60ch;
  text-wrap: pretty;
}

@media (prefers-reduced-motion: reduce) {
  .dmu-01__btn {
    transition: none;
  }
}

@media (forced-colors: active) {
  .dmu-01__card,
    .dmu-01__panel,
    .dmu-01__notes,
    .dmu-01__row {
    border-color: CanvasText;
    box-shadow: none;
  }

  .dmu-01__btn {
    background: ButtonFace;
    color: ButtonText;
    border: 1px solid ButtonText;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Dark Mode UI from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Dark Mode UI with prefers-color-scheme Root Strategy
Source: https://codefronts.com/design-styles/css-dark-mode-ui/dark-mode-ui-with-prefers-color-scheme-root-strategy/

The baseline pattern every other dark-mode technique builds on: declare your token block once on the component root, then let one media query re-declare the same token names for dark. No toggle, no attribute, no script — the UI simply obeys the operating system. Three panels render identical markup: a forced-light reference, a forced-dark reference, and one live panel that follows your current OS preference right now.
## HTML
```html
<div class="dmu-01">
  <!-- Pair this component with, in your document head:
       <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
       <meta name="theme-color" media="(prefers-color-scheme: dark)"  content="#09090b"> -->
  <div class="dmu-01__stage">
    <header class="dmu-01__head">
      <h1 class="dmu-01__h1">Root token strategy</h1>
      <p class="dmu-01__lede">One token block, one media query. Identical markup, three renderings.</p>
    </header>

    <div class="dmu-01__grid">
      <section class="dmu-01__panel dmu-01__panel--light">
        <p class="dmu-01__badge">Forced light reference</p>
        <article class="dmu-01__card">
          <div class="dmu-01__cardTop">
            <span class="dmu-01__dot" aria-hidden="true"></span>
            <span class="dmu-01__cardKicker">Deploy</span>
          </div>
          <h2 class="dmu-01__cardTitle">edge-runtime v4.2</h2>
          <p class="dmu-01__cardBody">Promoted to production 14 minutes ago by Alex Chen.</p>
          <div class="dmu-01__cardFoot">
            <button class="dmu-01__btn" type="button">View build</button>
            <span class="dmu-01__meta">iad1</span>
          </div>
        </article>
      </section>

      <section class="dmu-01__panel dmu-01__panel--dark">
        <p class="dmu-01__badge">Forced dark reference</p>
        <article class="dmu-01__card">
          <div class="dmu-01__cardTop">
            <span class="dmu-01__dot" aria-hidden="true"></span>
            <span class="dmu-01__cardKicker">Deploy</span>
          </div>
          <h2 class="dmu-01__cardTitle">edge-runtime v4.2</h2>
          <p class="dmu-01__cardBody">Promoted to production 14 minutes ago by Alex Chen.</p>
          <div class="dmu-01__cardFoot">
            <button class="dmu-01__btn" type="button">View build</button>
            <span class="dmu-01__meta">iad1</span>
          </div>
        </article>
      </section>

      <section class="dmu-01__panel dmu-01__panel--live">
        <p class="dmu-01__badge">
          <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><rect x="2.5" y="4" width="19" height="13" rx="2"/><path d="M8.5 20.5h7"/><path d="M12 17v3.5"/></svg>
          Live &mdash; follows your OS
        </p>
        <article class="dmu-01__card">
          <div class="dmu-01__cardTop">
            <span class="dmu-01__dot" aria-hidden="true"></span>
            <span class="dmu-01__cardKicker">Deploy</span>
          </div>
          <h2 class="dmu-01__cardTitle">edge-runtime v4.2</h2>
          <p class="dmu-01__cardBody">Promoted to production 14 minutes ago by Alex Chen.</p>
          <div class="dmu-01__cardFoot">
            <button class="dmu-01__btn" type="button">View build</button>
            <span class="dmu-01__meta">iad1</span>
          </div>
        </article>
      </section>
    </div>

    <aside class="dmu-01__notes">
      <h3 class="dmu-01__notesTitle">What the media query flips</h3>
      <dl class="dmu-01__tokens">
        <div class="dmu-01__row"><dt>--bg</dt><dd><span class="dmu-01__sw" style="background:#ffffff"></span>#ffffff<span class="dmu-01__arrow" aria-hidden="true">&rarr;</span><span class="dmu-01__sw" style="background:#09090b"></span>#09090b</dd></div>
        <div class="dmu-01__row"><dt>--surface</dt><dd><span class="dmu-01__sw" style="background:#f6f6f7"></span>#f6f6f7<span class="dmu-01__arrow" aria-hidden="true">&rarr;</span><span class="dmu-01__sw" style="background:#121216"></span>#121216</dd></div>
        <div class="dmu-01__row"><dt>--ink</dt><dd><span class="dmu-01__sw" style="background:#0a0a0b"></span>#0a0a0b<span class="dmu-01__arrow" aria-hidden="true">&rarr;</span><span class="dmu-01__sw" style="background:#f4f4f5"></span>#f4f4f5</dd></div>
        <div class="dmu-01__row"><dt>--line</dt><dd><span class="dmu-01__sw" style="background:#e3e3e7"></span>#e3e3e7<span class="dmu-01__arrow" aria-hidden="true">&rarr;</span><span class="dmu-01__sw" style="background:#26262c"></span>#26262c</dd></div>
        <div class="dmu-01__row"><dt>--accent</dt><dd><span class="dmu-01__sw" style="background:#4f46e5"></span>#4f46e5<span class="dmu-01__arrow" aria-hidden="true">&rarr;</span><span class="dmu-01__sw" style="background:#8b8bff"></span>#8b8bff</dd></div>
      </dl>
      <p class="dmu-01__notesFoot">No toggle ships here on purpose: this is the pure OS-driven baseline. Layer an override on top only when a visitor needs to disagree with their system.</p>
    </aside>
  </div>
</div>
```
## CSS
```css
.dmu-01 {
  --bg: #ffffff;
  --surface: #f6f6f7;
  --elevated: #ffffff;
  --ink: #0a0a0b;
  --muted: #5b5f66;
  --line: #e3e3e7;
  --accent: #4f46e5;
  --accent-ink: #ffffff;
  --focus: #4f46e5;
  --dot: #16a34a;
  --shadow: rgba(9,9,11,.10);
  --sans: system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono: ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;
  color-scheme: light dark;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

@media (prefers-color-scheme: dark) {
  .dmu-01 {
    --bg: #09090b;
    --surface: #121216;
    --elevated: #17171c;
    --ink: #f4f4f5;
    --muted: #a1a1aa;
    --line: #26262c;
    --accent: #8b8bff;
    --accent-ink: #0a0a12;
    --focus: #a5b4fc;
    --dot: #4ade80;
    --shadow: rgba(0,0,0,.6);
  }
}

@supports (color: oklch(50% .1 250)) {
  .dmu-01 {
    --bg: oklch(100% 0 0);
    --surface: oklch(97% .003 285);
    --ink: oklch(14% .01 285);
    --accent: oklch(50% .21 275);
  }

  @media (prefers-color-scheme: dark) {
    .dmu-01 {
      --bg: oklch(14% .008 285);
      --surface: oklch(19% .012 285);
      --ink: oklch(96% .004 285);
      --accent: oklch(76% .13 280);
    }
  }
}

.dmu-01,
.dmu-01 *,
.dmu-01 *::before,
.dmu-01 *::after {
  box-sizing: border-box;
}

.dmu-01__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  align-content: center;
  gap: clamp(1.5rem,3vw,2.5rem);
  padding: clamp(1.25rem,5vw,3.5rem);
  max-width: 82rem;
  margin-inline: auto;
}

.dmu-01__head {
  display: grid;
  gap: .45rem;
}

.dmu-01__h1 {
  margin: 0;
  font-size: clamp(1.35rem,3vw,1.9rem);
  font-weight: 650;
  letter-spacing: -.025em;
}

.dmu-01__lede {
  margin: 0;
  color: var(--muted);
  font-size: .95rem;
}

.dmu-01__grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit,minmax(15rem,1fr));
}

.dmu-01__panel {
  display: grid;
  gap: .75rem;
  padding: 1rem;
  border-radius: 1rem;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink);
}

.dmu-01__panel--light {
  --bg: #ffffff;
  --surface: #f6f6f7;
  --elevated: #ffffff;
  --ink: #0a0a0b;
  --muted: #5b5f66;
  --line: #e3e3e7;
  --accent: #4f46e5;
  --accent-ink: #ffffff;
  --dot: #16a34a;
  --shadow: rgba(9,9,11,.10);
}

.dmu-01__panel--dark {
  --bg: #09090b;
  --surface: #121216;
  --elevated: #17171c;
  --ink: #f4f4f5;
  --muted: #a1a1aa;
  --line: #26262c;
  --accent: #8b8bff;
  --accent-ink: #0a0a12;
  --dot: #4ade80;
  --shadow: rgba(0,0,0,.6);
}

.dmu-01__badge {
  margin: 0;
  display: flex;
  align-items: center;
  gap: .4rem;
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}

.dmu-01__badge svg {
  width: .95rem;
  height: .95rem;
}

.dmu-01__card {
  display: grid;
  gap: .6rem;
  padding: 1.05rem;
  border-radius: .85rem;
  background: var(--elevated);
  border: 1px solid var(--line);
  box-shadow: 0 1px 2px var(--shadow), 0 12px 28px -20px var(--shadow);
}

.dmu-01__cardTop {
  display: flex;
  align-items: center;
  gap: .5rem;
}

.dmu-01__dot {
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: var(--dot);
  box-shadow: 0 0 0 3px color-mix(in oklab,var(--dot) 22%,transparent);
}

.dmu-01__cardKicker {
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

.dmu-01__cardTitle {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -.02em;
  font-family: var(--mono);
}

.dmu-01__cardBody {
  margin: 0;
  font-size: .875rem;
  line-height: 1.55;
  color: var(--muted);
  text-wrap: pretty;
}

.dmu-01__cardFoot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  margin-top: .15rem;
}

.dmu-01__btn {
  min-height: 2.5rem;
  padding: 0 .95rem;
  border: 1px solid transparent;
  border-radius: .55rem;
  font: inherit;
  font-size: .85rem;
  font-weight: 550;
  cursor: pointer;
  background: var(--accent);
  color: var(--accent-ink);
  transition: filter .18s ease, translate .18s ease;
}

.dmu-01__btn:hover {
  filter: brightness(1.08);
  translate: 0 -1px;
}

.dmu-01__btn:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.dmu-01__meta {
  font-family: var(--mono);
  font-size: .75rem;
  color: var(--muted);
}

.dmu-01__notes {
  display: grid;
  gap: .85rem;
  padding: clamp(1rem,2.5vw,1.5rem);
  border-radius: 1rem;
  background: var(--surface);
  border: 1px solid var(--line);
}

.dmu-01__notesTitle {
  margin: 0;
  font-size: .95rem;
  font-weight: 600;
  letter-spacing: -.01em;
}

.dmu-01__tokens {
  margin: 0;
  display: grid;
  gap: .35rem;
}

.dmu-01__row {
  display: grid;
  grid-template-columns: minmax(6rem,9rem) 1fr;
  gap: .75rem;
  align-items: center;
  padding: .45rem .55rem;
  border-radius: .5rem;
  background: var(--bg);
  border: 1px solid var(--line);
}

.dmu-01__row dt {
  font-family: var(--mono);
  font-size: .78rem;
  color: var(--accent);
}

.dmu-01__row dd {
  margin: 0;
  display: flex;
  align-items: center;
  gap: .4rem;
  flex-wrap: wrap;
  font-family: var(--mono);
  font-size: .72rem;
  color: var(--muted);
}

.dmu-01__sw {
  width: .85rem;
  height: .85rem;
  border-radius: .25rem;
  border: 1px solid var(--line);
}

.dmu-01__arrow {
  padding-inline: .3rem;
  color: var(--ink);
}

.dmu-01__notesFoot {
  margin: 0;
  font-size: .83rem;
  line-height: 1.6;
  color: var(--muted);
  max-width: 60ch;
  text-wrap: pretty;
}

@media (prefers-reduced-motion: reduce) {
  .dmu-01__btn {
    transition: none;
  }
}

@media (forced-colors: active) {
  .dmu-01__card,
    .dmu-01__panel,
    .dmu-01__notes,
    .dmu-01__row {
    border-color: CanvasText;
    box-shadow: none;
  }

  .dmu-01__btn {
    background: ButtonFace;
    color: ButtonText;
    border: 1px solid ButtonText;
  }
}
```

How this works

Every colour in the component is a variable, and the variables are the only thing the media query touches. Markup, layout and specificity never change — that is what makes the pattern safe to retrofit into a shipped component:

.dmu-01{
  --bg:#ffffff; --surface:#f6f6f7; --ink:#0a0a0b;
  --muted:#5b5f66; --line:#e3e3e7; --accent:#4f46e5;
}
@media (prefers-color-scheme: dark){
  .dmu-01{
    --bg:#09090b; --surface:#121216; --ink:#f4f4f5;
    --muted:#a1a1aa; --line:#26262c; --accent:#8b8bff;
  }
}

The two reference panels use the same token names on a modifier class so a designer can see both states side by side on any machine, which is how you audit contrast without rebooting into the other theme:

.dmu-01__panel--light{ --bg:#ffffff; --ink:#0a0a0b; /* … */ }
.dmu-01__panel--dark { --bg:#09090b; --ink:#f4f4f5; /* … */ }

Note that the dark accent is lighter than the light-mode accent. Saturated mid-tones that pass on white fail on near-black, so accents get lifted in lightness and pulled back in chroma when the theme flips — the demo annotates each token pair with the value it flips to.

Make it yours

  • Rename the tokens to whatever your system uses — the pattern cares only that light and dark declare the same names, so nothing downstream has to change.
  • Add tokens in pairs. The moment one theme declares a variable the other does not, you get an unstyled element in exactly one mode and nobody notices for a week.
  • Move the token block from .dmu-01 to :root to theme a whole document instead of one component.
  • Bias the neutrals toward your accent hue (these are violet-tinted greys, not dead grey) by nudging the hue channel; it reads far more expensive than pure #111.
  • Drop the annotation column and the panel grid collapses to a single card — the token block is the reusable part.

Gotchas — read before shipping

  • prefers-color-scheme reports the OS/browser setting only. It is not a user preference for your site — if visitors need to disagree with their OS, you need demo 02's attribute override on top.
  • Also set color-scheme: light dark on the root so native controls, scrollbars and form widgets flip with you; without it you get a white scrollbar nailed to a black page.
  • Ship <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#09090b"> so mobile browser chrome matches the surface.
  • Do not paint the dark surface pure #000 unless you are targeting OLED specifically (see demo 11) — a slight lift stops shadows and borders from disappearing.
  • Text on dark needs less weight, not more: a 600 that looks right on white blooms on near-black. Drop a step when the theme flips.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

Floor set by oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 76+.

prefers-color-scheme and CSS custom properties are universally supported. color-scheme needs Chrome 81 / Safari 13 / Firefox 96. The oklch() upgrade sits behind @supports with hex declared first, so unsupported browsers keep the sRGB tokens.

Techniques used in this demo

Search CodeFronts

Loading…