Back to CSS Navbars CSS Navbar with Dark Mode Toggle CSS + JS
Share
HTML
<section class="nb-drk" data-theme="light" aria-label="CSS navbar with dark mode toggle demo">
  <div class="theme-flash" data-nb-drk-flash aria-hidden="true"></div>

  <nav aria-label="Primary">
    <div class="nav-inner">
      <a href="#" class="brand">
        <div class="brand-mark">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" aria-hidden="true"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
        </div>
        <div>
          <div class="brand-name">Linea</div>
          <div class="brand-tag">v3.2.0</div>
        </div>
      </a>

      <ul class="nav-links">
        <li><a href="#" class="active"><span class="link-dot"></span>Overview</a></li>
        <li><a href="#"><span class="link-dot"></span>Components</a></li>
        <li><a href="#"><span class="link-dot"></span>Templates</a></li>
        <li><a href="#"><span class="link-dot"></span>Docs</a></li>
        <li><a href="#"><span class="link-dot"></span>Changelog</a></li>
      </ul>

      <div class="nav-spacer"></div>

      <div class="nav-actions">
        <button class="theme-toggle" type="button" data-nb-drk-toggle aria-label="Toggle dark mode">
          <svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
          <div class="toggle-track"><div class="toggle-thumb"></div></div>
          <svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
          <span class="toggle-label" data-nb-drk-label>Light</span>
        </button>
        <div class="nav-div" aria-hidden="true"></div>
        <button class="btn-ghost" type="button">Sign in</button>
        <button class="btn-fill" type="button">Get started</button>
      </div>
    </div>
  </nav>

  <div class="page">
    <section class="hero">
      <div class="hero-left">
        <div class="hero-pill">Now in v3 — See what's new</div>
        <h1>Design tokens<br>that <em>adapt</em><br>with you.</h1>
        <p class="hero-body">Every color, shadow, and surface in this UI is driven by semantic CSS custom properties. Watch them all flip in under half a second — click the toggle above.</p>
        <div class="hero-ctas">
          <button class="cta-main" type="button">Browse Components →</button>
          <a href="#" class="cta-link">
            View on GitHub
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>
          </a>
        </div>
      </div>
      <div class="hero-right">
        <div class="preview-panel">
          <p class="preview-label">Live token preview — toggle the theme above</p>
          <div class="token-grid">
            <div class="token-card">
              <div class="token-swatch" data-nb-drk-swatch="bg"></div>
              <div class="token-name">--bg</div>
              <div class="token-val" data-nb-drk-val="bg">—</div>
            </div>
            <div class="token-card">
              <div class="token-swatch" data-nb-drk-swatch="text"></div>
              <div class="token-name">--text</div>
              <div class="token-val" data-nb-drk-val="text">—</div>
            </div>
            <div class="token-card">
              <div class="token-swatch" data-nb-drk-swatch="accent"></div>
              <div class="token-name">--accent</div>
              <div class="token-val" data-nb-drk-val="accent">—</div>
            </div>
            <div class="token-card">
              <div class="token-swatch" data-nb-drk-swatch="border"></div>
              <div class="token-name">--border</div>
              <div class="token-val" data-nb-drk-val="border">—</div>
            </div>
          </div>
          <div class="code-block">
<span class="code-attr">[data-theme=<span class="code-val">"<span data-nb-drk-theme-in-code>light</span>"</span>]</span> {<br>
&nbsp;&nbsp;<span class="code-attr">--bg</span>: <span class="code-val" data-nb-drk-code="bg">#fafaf8</span>;<br>
&nbsp;&nbsp;<span class="code-attr">--text</span>: <span class="code-val" data-nb-drk-code="text">#18181a</span>;<br>
&nbsp;&nbsp;<span class="code-attr">--accent</span>: <span class="code-val" data-nb-drk-code="accent">#e84a2f</span>;<br>
&nbsp;&nbsp;<span class="code-attr">--surface</span>: <span class="code-val" data-nb-drk-code="surface">#ffffff</span>;<br>
}
          </div>
          <div class="stat-strip">
            <div class="stat-item"><div class="stat-val">48</div><div class="stat-lbl">Tokens</div></div>
            <div class="stat-item"><div class="stat-val">2</div><div class="stat-lbl">Themes</div></div>
            <div class="stat-item"><div class="stat-val">0.45s</div><div class="stat-lbl">Transition</div></div>
          </div>
        </div>
      </div>
    </section>

    <div class="features">
      <div class="feature">
        <div class="feature-icon">◑</div>
        <div class="feature-title">Semantic tokens</div>
        <div class="feature-desc">Every value maps to intent, not raw colors. Theme by switching a single attribute.</div>
      </div>
      <div class="feature">
        <div class="feature-icon">⚡</div>
        <div class="feature-title">CSS-only transitions</div>
        <div class="feature-desc">No JavaScript required for the color shift. Pure CSS custom properties do the work.</div>
      </div>
      <div class="feature">
        <div class="feature-icon">♿</div>
        <div class="feature-title">WCAG AA+</div>
        <div class="feature-desc">Both themes pass contrast requirements for text, borders, and interactive states.</div>
      </div>
      <div class="feature">
        <div class="feature-icon">💾</div>
        <div class="feature-title">Persisted preference</div>
        <div class="feature-desc">Choice is saved to localStorage and respects prefers-color-scheme on first load.</div>
      </div>
    </div>
  </div>
</section>
CSS
/* ─── 07 Linea — CSS navbar with dark mode toggle UI ──────────── */
@import url('https://fonts.googleapis.com/css2?family=Epilogue:ital,wght@0,300;0,400;0,500;0,700;0,900;1,300;1,700&family=Fira+Code:wght@300;400;500&display=swap');

.nb-drk {
  --nb-drk-ease: cubic-bezier(0.4, 0, 0.2, 1);
  --nb-drk-dur: 0.45s;

  position: relative;
  width: 100%;
  min-height: 720px;
  font-family: 'Epilogue', sans-serif;
  overflow: hidden;
  box-sizing: border-box;
  transition: background var(--nb-drk-dur) var(--nb-drk-ease), color var(--nb-drk-dur) var(--nb-drk-ease);
}
.nb-drk *, .nb-drk *::before, .nb-drk *::after { box-sizing: border-box; margin: 0; padding: 0; }

.nb-drk[data-theme="light"] {
  --nb-drk-bg:            #fafaf8;
  --nb-drk-bg-2:          #f2f1ee;
  --nb-drk-surface:       #ffffff;
  --nb-drk-surface-float: rgba(255,255,255,0.85);
  --nb-drk-border:        #e4e2dc;
  --nb-drk-border-soft:   rgba(0,0,0,0.06);
  --nb-drk-text:          #18181a;
  --nb-drk-text-2:        #5a5856;
  --nb-drk-text-3:        #a09e99;
  --nb-drk-accent:        #e84a2f;
  --nb-drk-accent-bg:     rgba(232,74,47,0.08);
  --nb-drk-toggle-track:  #e4e2dc;
  --nb-drk-toggle-thumb:  #18181a;
  --nb-drk-shadow-nav:    0 1px 0 var(--nb-drk-border), 0 4px 24px rgba(0,0,0,0.05);
  --nb-drk-shadow-card:   0 2px 16px rgba(0,0,0,0.07);
  --nb-drk-invert-text:   #fafaf8;
}
.nb-drk[data-theme="dark"] {
  --nb-drk-bg:            #111113;
  --nb-drk-bg-2:          #19191c;
  --nb-drk-surface:       #1e1e22;
  --nb-drk-surface-float: rgba(22,22,26,0.88);
  --nb-drk-border:        #2e2e34;
  --nb-drk-border-soft:   rgba(255,255,255,0.06);
  --nb-drk-text:          #f0efec;
  --nb-drk-text-2:        #9e9c97;
  --nb-drk-text-3:        #5c5a57;
  --nb-drk-accent:        #ff6b4a;
  --nb-drk-accent-bg:     rgba(255,107,74,0.1);
  --nb-drk-toggle-track:  #2e2e34;
  --nb-drk-toggle-thumb:  #f0efec;
  --nb-drk-shadow-nav:    0 1px 0 var(--nb-drk-border), 0 4px 32px rgba(0,0,0,0.3);
  --nb-drk-shadow-card:   0 2px 20px rgba(0,0,0,0.3);
  --nb-drk-invert-text:   #111113;
}
.nb-drk { background: var(--nb-drk-bg); color: var(--nb-drk-text); }

.nb-drk nav { position: absolute; top: 0; left: 0; right: 0; z-index: 100; height: 64px; background: var(--nb-drk-surface-float); backdrop-filter: blur(20px) saturate(150%); -webkit-backdrop-filter: blur(20px) saturate(150%); border-bottom: 1px solid var(--nb-drk-border); box-shadow: var(--nb-drk-shadow-nav); transition: background var(--nb-drk-dur) var(--nb-drk-ease), border-color var(--nb-drk-dur) var(--nb-drk-ease), box-shadow var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .nav-inner { max-width: 1280px; margin: 0 auto; height: 100%; display: flex; align-items: center; padding: 0 2rem; gap: 2rem; }
.nb-drk .brand { text-decoration: none; display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.nb-drk .brand-mark { width: 28px; height: 28px; border-radius: 7px; background: var(--nb-drk-accent); display: flex; align-items: center; justify-content: center; flex-shrink: 0; transition: background var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .brand-mark svg { display: block; }
.nb-drk .brand-name { font-size: 1rem; font-weight: 700; letter-spacing: -0.02em; color: var(--nb-drk-text); transition: color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .brand-tag { font-family: 'Fira Code', monospace; font-size: 0.6rem; font-weight: 300; color: var(--nb-drk-text-3); letter-spacing: 0.04em; transition: color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .nav-links { display: flex; align-items: center; gap: 0; list-style: none; flex: 1; }
.nb-drk .nav-links a { display: flex; align-items: center; gap: 6px; font-size: 0.8rem; font-weight: 500; color: var(--nb-drk-text-2); text-decoration: none; padding: 7px 13px; border-radius: 8px; transition: color var(--nb-drk-dur) var(--nb-drk-ease), background 0.18s var(--nb-drk-ease); white-space: nowrap; }
.nb-drk .nav-links a:hover { color: var(--nb-drk-text); background: var(--nb-drk-border-soft); }
.nb-drk .nav-links a.active { color: var(--nb-drk-text); background: var(--nb-drk-accent-bg); }
.nb-drk .nav-links a.active .link-dot { background: var(--nb-drk-accent); }
.nb-drk .link-dot { width: 5px; height: 5px; border-radius: 50%; background: transparent; flex-shrink: 0; transition: background var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .nav-spacer { flex: 1; }
.nb-drk .nav-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.nb-drk .theme-toggle { display: flex; align-items: center; gap: 9px; background: none; border: none; cursor: pointer; padding: 6px 10px; border-radius: 10px; transition: background 0.18s; font-family: inherit; color: inherit; }
.nb-drk .theme-toggle:hover { background: var(--nb-drk-border-soft); }
.nb-drk .toggle-track { width: 36px; height: 20px; border-radius: 100px; background: var(--nb-drk-toggle-track); border: 1.5px solid var(--nb-drk-border); position: relative; transition: background var(--nb-drk-dur) var(--nb-drk-ease), border-color var(--nb-drk-dur) var(--nb-drk-ease); flex-shrink: 0; }
.nb-drk .toggle-thumb { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--nb-drk-toggle-thumb); transition: transform var(--nb-drk-dur) cubic-bezier(0.34, 1.56, 0.64, 1), background var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk[data-theme="dark"] .toggle-thumb { transform: translateX(16px); }
.nb-drk .icon-sun, .nb-drk .icon-moon { width: 14px; height: 14px; transition: opacity 0.3s var(--nb-drk-ease), transform 0.4s var(--nb-drk-ease), color var(--nb-drk-dur) var(--nb-drk-ease); color: var(--nb-drk-text-2); }
.nb-drk[data-theme="light"] .icon-sun { opacity: 1; transform: rotate(0deg); }
.nb-drk[data-theme="light"] .icon-moon { opacity: 0.3; }
.nb-drk[data-theme="dark"] .icon-moon { opacity: 1; transform: rotate(-15deg); }
.nb-drk[data-theme="dark"] .icon-sun { opacity: 0.3; }
.nb-drk .toggle-label { font-family: 'Fira Code', monospace; font-size: 0.65rem; color: var(--nb-drk-text-3); transition: color var(--nb-drk-dur) var(--nb-drk-ease); white-space: nowrap; }
.nb-drk .nav-div { width: 1px; height: 20px; background: var(--nb-drk-border); transition: background var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .btn-ghost { font-family: 'Epilogue', sans-serif; font-size: 0.78rem; font-weight: 500; color: var(--nb-drk-text-2); background: none; border: 1px solid var(--nb-drk-border); border-radius: 8px; padding: 7px 15px; cursor: pointer; transition: color var(--nb-drk-dur) var(--nb-drk-ease), border-color var(--nb-drk-dur) var(--nb-drk-ease), background 0.18s; white-space: nowrap; }
.nb-drk .btn-ghost:hover { color: var(--nb-drk-text); border-color: var(--nb-drk-text-3); }
.nb-drk .btn-fill { font-family: 'Epilogue', sans-serif; font-size: 0.78rem; font-weight: 700; color: var(--nb-drk-invert-text); background: var(--nb-drk-text); border: none; border-radius: 8px; padding: 7px 16px; cursor: pointer; transition: background var(--nb-drk-dur) var(--nb-drk-ease), color var(--nb-drk-dur) var(--nb-drk-ease), transform 0.15s; white-space: nowrap; }
.nb-drk .btn-fill:hover { transform: translateY(-1px); }

.nb-drk .page { padding-top: 64px; }
.nb-drk .hero { min-height: 560px; display: grid; grid-template-columns: 1fr 1fr; }
.nb-drk .hero-left { padding: 5rem 4rem; display: flex; flex-direction: column; justify-content: center; border-right: 1px solid var(--nb-drk-border); transition: border-color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .hero-pill { display: inline-flex; align-items: center; gap: 8px; font-family: 'Fira Code', monospace; font-size: 0.65rem; font-weight: 400; color: var(--nb-drk-accent); background: var(--nb-drk-accent-bg); border: 1px solid rgba(232,74,47,0.15); border-radius: 100px; padding: 5px 12px; margin-bottom: 2rem; width: fit-content; transition: color var(--nb-drk-dur) var(--nb-drk-ease), background var(--nb-drk-dur) var(--nb-drk-ease), border-color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .hero-pill::before { content: '◉'; font-size: 0.55rem; }
.nb-drk h1 { font-size: clamp(2.8rem, 4.5vw, 5rem); font-weight: 900; letter-spacing: -0.04em; line-height: 1.0; color: var(--nb-drk-text); margin-bottom: 1.5rem; transition: color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk h1 em { font-style: italic; font-weight: 300; color: var(--nb-drk-accent); transition: color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .hero-body { font-size: 1rem; color: var(--nb-drk-text-2); line-height: 1.7; max-width: 42ch; margin-bottom: 2.5rem; transition: color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .hero-ctas { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.nb-drk .cta-main { font-family: 'Epilogue', sans-serif; font-size: 0.82rem; font-weight: 700; color: var(--nb-drk-invert-text); background: var(--nb-drk-text); border: none; border-radius: 10px; padding: 12px 22px; cursor: pointer; transition: background var(--nb-drk-dur) var(--nb-drk-ease), color var(--nb-drk-dur) var(--nb-drk-ease), transform 0.15s; }
.nb-drk .cta-main:hover { transform: translateY(-2px); }
.nb-drk .cta-link { font-size: 0.82rem; font-weight: 500; color: var(--nb-drk-text-2); text-decoration: none; transition: color 0.2s; display: flex; align-items: center; gap: 6px; }
.nb-drk .cta-link:hover { color: var(--nb-drk-text); }

.nb-drk .hero-right { padding: 3rem; display: flex; align-items: center; justify-content: center; background: var(--nb-drk-bg-2); transition: background var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .preview-panel { width: 100%; max-width: 400px; display: flex; flex-direction: column; gap: 12px; }
.nb-drk .preview-label { font-family: 'Fira Code', monospace; font-size: 0.6rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--nb-drk-text-3); margin-bottom: 4px; transition: color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .token-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 4px; }
.nb-drk .token-card { background: var(--nb-drk-surface); border: 1px solid var(--nb-drk-border); border-radius: 10px; padding: 12px 14px; transition: background var(--nb-drk-dur) var(--nb-drk-ease), border-color var(--nb-drk-dur) var(--nb-drk-ease), box-shadow var(--nb-drk-dur) var(--nb-drk-ease); box-shadow: var(--nb-drk-shadow-card); }
.nb-drk .token-swatch { width: 20px; height: 20px; border-radius: 5px; margin-bottom: 8px; border: 1px solid var(--nb-drk-border-soft); }
.nb-drk .token-name { font-family: 'Fira Code', monospace; font-size: 0.6rem; color: var(--nb-drk-text-3); transition: color var(--nb-drk-dur) var(--nb-drk-ease); margin-bottom: 2px; }
.nb-drk .token-val { font-family: 'Fira Code', monospace; font-size: 0.65rem; color: var(--nb-drk-text); font-weight: 500; transition: color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .code-block { background: var(--nb-drk-surface); border: 1px solid var(--nb-drk-border); border-radius: 10px; padding: 14px 16px; font-family: 'Fira Code', monospace; font-size: 0.68rem; line-height: 1.8; color: var(--nb-drk-text-2); transition: background var(--nb-drk-dur) var(--nb-drk-ease), border-color var(--nb-drk-dur) var(--nb-drk-ease), color var(--nb-drk-dur) var(--nb-drk-ease); box-shadow: var(--nb-drk-shadow-card); }
.nb-drk .code-attr { color: var(--nb-drk-accent); transition: color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .code-val { color: var(--nb-drk-text); transition: color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .stat-strip { display: flex; gap: 0; background: var(--nb-drk-surface); border: 1px solid var(--nb-drk-border); border-radius: 10px; overflow: hidden; box-shadow: var(--nb-drk-shadow-card); transition: background var(--nb-drk-dur) var(--nb-drk-ease), border-color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .stat-item { flex: 1; padding: 12px 14px; border-right: 1px solid var(--nb-drk-border); transition: border-color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .stat-item:last-child { border-right: none; }
.nb-drk .stat-val { font-size: 1.2rem; font-weight: 900; letter-spacing: -0.03em; color: var(--nb-drk-text); line-height: 1; margin-bottom: 3px; transition: color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .stat-lbl { font-size: 0.62rem; color: var(--nb-drk-text-3); font-weight: 500; letter-spacing: 0.04em; transition: color var(--nb-drk-dur) var(--nb-drk-ease); }

.nb-drk .theme-flash { position: absolute; inset: 0; z-index: 99; pointer-events: none; opacity: 0; border-radius: 50%; transform: scale(0); }
.nb-drk .theme-flash.animate { animation: nb-drk-ripple 0.55s ease-out forwards; }
@keyframes nb-drk-ripple {
  0%   { opacity: 0.12; transform: scale(0); border-radius: 50%; }
  60%  { opacity: 0.06; transform: scale(3); }
  100% { opacity: 0; transform: scale(5); }
}

.nb-drk .features { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid var(--nb-drk-border); transition: border-color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .feature { padding: 2.5rem 2rem; border-right: 1px solid var(--nb-drk-border); transition: background 0.2s, border-color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .feature:last-child { border-right: none; }
.nb-drk .feature:hover { background: var(--nb-drk-bg-2); }
.nb-drk .feature-icon { width: 36px; height: 36px; border-radius: 9px; background: var(--nb-drk-accent-bg); display: flex; align-items: center; justify-content: center; margin-bottom: 1rem; color: var(--nb-drk-accent); font-size: 1rem; transition: background var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .feature-title { font-size: 0.88rem; font-weight: 700; color: var(--nb-drk-text); margin-bottom: 0.4rem; letter-spacing: -0.01em; transition: color var(--nb-drk-dur) var(--nb-drk-ease); }
.nb-drk .feature-desc { font-size: 0.78rem; color: var(--nb-drk-text-2); line-height: 1.6; transition: color var(--nb-drk-dur) var(--nb-drk-ease); }

@media (max-width: 900px) {
  .nb-drk .hero { grid-template-columns: 1fr; }
  .nb-drk .hero-left { border-right: none; border-bottom: 1px solid var(--nb-drk-border); padding: 3rem 2rem; }
  .nb-drk .hero-right { padding: 2rem; }
  .nb-drk .features { grid-template-columns: 1fr 1fr; }
  .nb-drk .feature:nth-child(2) { border-right: none; }
  .nb-drk .feature:nth-child(3), .nb-drk .feature:nth-child(4) { border-top: 1px solid var(--nb-drk-border); }
  .nb-drk .nav-links { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .nb-drk *, .nb-drk *::before, .nb-drk *::after { transition: none !important; animation: none !important; }
}
JS
(() => {
  // Scoped dark-mode toggle. Theme attribute lives on the wrapper, not
  // <html>, so multiple instances on a page each carry their own theme
  // state. The source mock used localStorage + prefers-color-scheme —
  // both dropped because the embedded demo doesn't own the host page's
  // theme preference. The radial flash overlay is bounded to the
  // wrapper (position:absolute) so it doesn't escape into the gallery.
  const root = document.querySelector('.nb-drk');
  if (!root) return;
  const toggle = root.querySelector('[data-nb-drk-toggle]');
  const label = root.querySelector('[data-nb-drk-label]');
  const flash = root.querySelector('[data-nb-drk-flash]');
  if (!toggle || !label || !flash) return;

  const tokens = {
    light: { bg: '#fafaf8', text: '#18181a', accent: '#e84a2f', border: '#e4e2dc', surface: '#ffffff' },
    dark:  { bg: '#111113', text: '#f0efec', accent: '#ff6b4a', border: '#2e2e34', surface: '#1e1e22' },
  };

  function updatePreview(theme) {
    const t = tokens[theme];
    for (const key of ['bg', 'text', 'accent', 'border']) {
      const valEl = root.querySelector('[data-nb-drk-val="' + key + '"]');
      const swatchEl = root.querySelector('[data-nb-drk-swatch="' + key + '"]');
      if (valEl) valEl.textContent = t[key];
      if (swatchEl) swatchEl.style.background = t[key];
    }
    for (const key of ['bg', 'text', 'accent', 'surface']) {
      const codeEl = root.querySelector('[data-nb-drk-code="' + key + '"]');
      if (codeEl) codeEl.textContent = t[key];
    }
    const themeInCode = root.querySelector('[data-nb-drk-theme-in-code]');
    if (themeInCode) themeInCode.textContent = theme;
    label.textContent = theme === 'dark' ? 'Dark' : 'Light';
  }

  function triggerFlash(theme) {
    flash.style.background = theme === 'dark' ? '#f0efec' : '#111113';
    flash.classList.remove('animate');
    // Force reflow so the animation restarts.
    void flash.offsetWidth;
    flash.classList.add('animate');
  }

  function applyTheme(theme) {
    root.setAttribute('data-theme', theme);
    updatePreview(theme);
  }

  // Seed from the wrapper's current data-theme (default "light" via HTML).
  applyTheme(root.getAttribute('data-theme') || 'light');

  toggle.addEventListener('click', () => {
    const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
    triggerFlash(next);
    applyTheme(next);
  });
})();