The famous day and night switch: a sun with halo rings rolls across a blue sky, a moon slides over it, clouds sink away and twinkling stars drop in. Built from spans and box-shadows.
Also known as: day night toggle animation, day and night toggle switch, sun to moon toggle.
<label class="tgl-day-night-toggle">
<input type="checkbox" role="switch" aria-label="Dark mode">
<span class="tgl-day-night-toggle-sky" aria-hidden="true">
<span class="tgl-day-night-toggle-stars"></span>
<span class="tgl-day-night-toggle-clouds"></span>
<span class="tgl-day-night-toggle-orb">
<span class="tgl-day-night-toggle-moon"></span>
</span>
</span>
</label>.tgl-day-night-toggle {
--c: var(--tgl-color, #3b82f6);
font-size: var(--tgl-size, 24px);
position: relative;
display: inline-flex;
align-items: center;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.tgl-day-night-toggle input {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.tgl-day-night-toggle-sky {
--ease: cubic-bezier(0.6, -0.2, 0.3, 1.15);
position: relative;
display: block;
width: 7em;
height: 3em;
border-radius: 3em;
overflow: hidden;
isolation: isolate;
background-color: color-mix(in srgb, var(--c) 70%, #7dd3fc);
transition: background-color 0.7s ease;
}
/* inner shadow drawn on top of the scene */
.tgl-day-night-toggle-sky::after {
content: "";
position: absolute;
inset: 0;
z-index: 3;
border-radius: inherit;
box-shadow: inset 0 0.15em 0.3em rgba(0, 0, 0, 0.35), inset 0 -0.06em 0.12em rgba(255, 255, 255, 0.3);
}
.tgl-day-night-toggle-orb {
position: absolute;
top: 0.3em;
left: 0.3em;
z-index: 2;
width: 2.4em;
height: 2.4em;
border-radius: 50%;
overflow: hidden;
background: #fbbf24;
box-shadow:
inset 0.12em 0.12em 0.2em rgba(255, 255, 255, 0.55),
inset -0.12em -0.14em 0.25em rgba(146, 64, 14, 0.45),
0.06em 0.12em 0.3em rgba(0, 0, 0, 0.35),
0 0 0 0.9em rgba(255, 255, 255, 0.1),
0 0 0 1.8em rgba(255, 255, 255, 0.07),
0 0 0 2.7em rgba(255, 255, 255, 0.05);
transition: transform 0.7s var(--ease);
}
.tgl-day-night-toggle-moon {
position: absolute;
inset: 0;
border-radius: 50%;
background:
radial-gradient(circle at 30% 32%, #94a3b8 0 0.28em, transparent 0.3em),
radial-gradient(circle at 68% 44%, #94a3b8 0 0.2em, transparent 0.22em),
radial-gradient(circle at 44% 74%, #94a3b8 0 0.32em, transparent 0.34em),
#cbd5e1;
box-shadow: inset 0.12em 0.12em 0.2em rgba(255, 255, 255, 0.6), inset -0.12em -0.14em 0.25em rgba(51, 65, 85, 0.5);
transform: translateX(110%);
transition: transform 0.7s var(--ease) 0.05s;
}
.tgl-day-night-toggle-clouds {
position: absolute;
inset: 0;
z-index: 1;
transition: transform 0.7s var(--ease);
}
.tgl-day-night-toggle-clouds::before,
.tgl-day-night-toggle-clouds::after {
content: "";
position: absolute;
width: 1.3em;
height: 1.3em;
border-radius: 50%;
}
.tgl-day-night-toggle-clouds::before {
left: 3em;
top: 1.5em;
--cloud: color-mix(in srgb, var(--c) 22%, #fff);
background: var(--cloud);
box-shadow:
0.9em -0.2em 0 0.05em var(--cloud),
1.85em -0.45em 0 0.12em var(--cloud),
2.75em -0.75em 0 0.2em var(--cloud),
3.5em -0.15em 0 0.3em var(--cloud);
}
.tgl-day-night-toggle-clouds::after {
left: 3.4em;
top: 2.05em;
background: #fff;
box-shadow:
0.85em -0.25em 0 0.08em #fff,
1.8em -0.05em 0 0.02em #fff,
2.6em -0.55em 0 0.2em #fff,
3.3em 0.15em 0 0.25em #fff,
1.3em 0.55em 0 0.2em #fff;
}
.tgl-day-night-toggle-stars {
position: absolute;
inset: 0;
opacity: 0;
transform: translateY(-2em);
transition: transform 0.7s var(--ease), opacity 0.5s ease;
}
.tgl-day-night-toggle-stars::before,
.tgl-day-night-toggle-stars::after {
content: "";
position: absolute;
border-radius: 50%;
background: #fff;
animation: tgl-day-night-toggle-twinkle 2.6s ease-in-out infinite;
}
.tgl-day-night-toggle-stars::before {
top: 0.55em;
left: 0.8em;
width: 0.16em;
height: 0.16em;
box-shadow: 0.9em 0.75em 0 0.02em #fff, 1.9em 0.1em 0 #fff, 2.6em 1.15em 0 0.03em #fff, 0.3em 1.45em 0 #fff, 3.4em 0.45em 0 0.02em #fff;
}
.tgl-day-night-toggle-stars::after {
top: 0.9em;
left: 1.3em;
width: 0.11em;
height: 0.11em;
box-shadow: 1.1em -0.35em 0 #fff, 0.5em 0.95em 0 0.01em #fff, 2.2em 0.6em 0 #fff, 3.1em 1.3em 0 0.01em #fff, -0.6em 0.4em 0 #fff;
animation-delay: -1.3s;
}
@keyframes tgl-day-night-toggle-twinkle {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* night */
.tgl-day-night-toggle input:checked + .tgl-day-night-toggle-sky {
background-color: color-mix(in srgb, var(--c) 22%, #0b1120);
}
.tgl-day-night-toggle input:checked + .tgl-day-night-toggle-sky .tgl-day-night-toggle-orb { transform: translateX(4em); }
.tgl-day-night-toggle input:checked + .tgl-day-night-toggle-sky .tgl-day-night-toggle-moon { transform: translateX(0); }
.tgl-day-night-toggle input:checked + .tgl-day-night-toggle-sky .tgl-day-night-toggle-clouds { transform: translateY(2.2em); }
.tgl-day-night-toggle input:checked + .tgl-day-night-toggle-sky .tgl-day-night-toggle-stars {
opacity: 1;
transform: translateY(0);
}
/* the orb leans toward the other side on hover */
.tgl-day-night-toggle:hover .tgl-day-night-toggle-orb { transform: translateX(0.2em); }
.tgl-day-night-toggle:hover input:checked + .tgl-day-night-toggle-sky .tgl-day-night-toggle-orb { transform: translateX(3.8em); }
.tgl-day-night-toggle input:focus-visible + .tgl-day-night-toggle-sky {
outline: 2px solid var(--c);
outline-offset: 0.2em;
}
@media (prefers-reduced-motion: reduce) {
.tgl-day-night-toggle, .tgl-day-night-toggle *, .tgl-day-night-toggle::before, .tgl-day-night-toggle::after, .tgl-day-night-toggle *::before, .tgl-day-night-toggle *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--tgl-color on the element or a parent.
Currently previewing #3b82f6.--tgl-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 24px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.