A sunrise and sunset theme switch
Light rises from the bottom, night comes down from the top. How I built it with the View Transitions API, and the three bugs I hit on the way.
- CSS
- View Transitions
- UX
This site has a light and a dark theme. I wanted switching between them to feel intentional, so the toggle plays a little sunrise or sunset: the light theme rises up from the bottom of the screen, and dark mode comes down from the top.
Attempt one: fade every colour
My first version added a short colour transition to every element while the theme changed. It flickered. Text inherits its colour from its parent, so a child was animating toward its parent's in-between colours, which were themselves still changing. My name in the hero visibly jumped through two colours.
Attempt two: View Transitions
document.startViewTransition() takes a snapshot of the page, lets you change the DOM, then animates from the old snapshot to the new one. Nothing fades element by element, so there's nothing to flicker. I mask the new snapshot with a soft gradient and move the mask up for sunrise or down for sunset.
root.dataset.themeTransition = theme === "light" ? "sunrise" : "sunset";
const transition = document.startViewTransition(() => {
root.dataset.theme = theme;
});Bugs worth knowing about
- Clicks didn't register mid-transition. While a view transition runs, the browser sends clicks to the root element, not to the button under your cursor. I catch document clicks inside the toggle's rectangle so the button always responds.
- Rapid clicks had to wait. A new click now calls skipTransition() on the running one and starts the next straight away.
- Skipping a transition rejects its ready promise, which showed up as a console error. That rejection is expected, so it is handled quietly.
With reduced motion turned on, or in a browser without View Transitions, the theme simply switches instantly.