Skip to content

Sun/Moon Morph

A copy-paste theme toggle component in pure HTML, CSS & vanilla JS. Zero dependencies, framework-agnostic, MIT-licensed.

Theme ToggleHTMLCSSJavaScriptany framework

Copy into your project

HTML
<!-- Sun/Moon Morph — toggle .is-dark on the button -->
<button class="nuda-sunmoon" type="button" aria-label="Toggle theme">
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
       stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    <circle class="nuda-sunmoon__sun" cx="12" cy="12" r="5" />
    <g class="nuda-sunmoon__rays">
      <path d="M12 2 L12 5" /><path d="M12 19 L12 22" />
      <path d="M2 12 L5 12" /><path d="M19 12 L22 12" />
      <path d="M4.9 4.9 L7 7" /><path d="M17 17 L19.1 19.1" />
      <path d="M4.9 19.1 L7 17" /><path d="M17 7 L19.1 4.9" />
    </g>
    <path class="nuda-sunmoon__moon"
          d="M21 13 A 9 9 0 1 1 11 3 A 7 7 0 0 0 21 13 Z" />
  </svg>
</button>
CSS
/* Sun/Moon Morph
   Sun (circle + rays) cross-fades into a moon path. Tilts on hover.
   Customize: --tt-accent */

.nuda-sunmoon {
  --tt-accent: #e4ff54;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  color: #fafafa;
  cursor: pointer;
  transition: background 0.25s, border-color 0.25s, transform 0.25s, color 0.25s;
}

.nuda-sunmoon:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(228, 255, 84, 0.3);
  transform: rotate(-12deg);
}

.nuda-sunmoon svg {
  width: 18px;
  height: 18px;
  overflow: visible;
}

.nuda-sunmoon__sun,
.nuda-sunmoon__rays,
.nuda-sunmoon__moon {
  transition:
    opacity 0.35s,
    transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  transform-origin: center;
}

/* Light mode default — show sun, hide moon */
.nuda-sunmoon:not(.is-dark) .nuda-sunmoon__moon {
  opacity: 0;
  transform: scale(0.5) rotate(-30deg);
}

/* Dark mode — show moon, hide sun */
.nuda-sunmoon.is-dark .nuda-sunmoon__sun,
.nuda-sunmoon.is-dark .nuda-sunmoon__rays {
  opacity: 0;
  transform: scale(0.5) rotate(30deg);
}

.nuda-sunmoon.is-dark { color: var(--tt-accent); }
JavaScript
/* Sun/Moon Morph — toggle and persist theme. */

(function () {
  var btn = document.querySelector('.nuda-sunmoon');
  if (!btn) return;
  var KEY = 'theme';

  // Initial state from preference / system.
  var saved = localStorage.getItem(KEY);
  var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  var dark = saved ? saved === 'dark' : prefersDark;

  apply();
  btn.addEventListener('click', function () {
    dark = !dark;
    apply();
    localStorage.setItem(KEY, dark ? 'dark' : 'light');
  });

  function apply() {
    btn.classList.toggle('is-dark', dark);
    document.documentElement.classList.toggle('dark', dark);
  }
})();

How to use Sun/Moon Morph

Paste the HTML where you need it and the CSS into a global stylesheet (or a <style> tag). Every class is prefixed nuda- so it never collides with Tailwind or your own styles. Tweak the CSS custom properties to match your design system.

Works in React, Vue, Svelte, Astro, Next.js, Nuxt, Laravel Blade, Django, Rails — or a single .html file. No npm install, no build step.

More theme toggle components

← Browse all NudaUI components