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.