Edison bulb night mode
- Planted:
- Last watered:
Up in the top right corner of the screen, there’s a little light bulb. It’s an SVG I made to resemble the Edison bulb I have in my apartment. You can click or drag and release to turn the lights on and off. All in all it’s a bit over 100 lines of code.
Night owls and early birds
Upon your first visit, an effect hook runs that taps into OS settings via the prefers-color-scheme
CSS media query, defaulting to dark or light. You can play around with this by opening an incognito window and toggling your system theme setting or emulating CSS media features in Chrome DevTools.
When you click or drag the light bulb, your selection gets persisted in localStorage
so that upon returning to my garden it’ll be either day or night, just like when you last visited.
It’s worth noting one shortcoming of this approach w/r/t SSR: localStorage
and window.matchMedia
are only available client-side, so the server-rendered HTML will always default to light mode. This creates a flicker for night owls, which I considered addressing with some server persistence or a loading state before the initial effect runs. The former feels a bit heavy, and the latter doesn’t exactly solve the problem (would the loading UI be light or dark?). As a compromise, I apply a CSS transition to color
and background-color
to create a fade-in effect.
Tactile animation
The drag ’n pull animation relies on react-spring
, which aims to apply the fluidity of real world physics to web animations. Thanks to Josh Comeau’s introduction to spring physics and React Spring for the inspiration. Josh links to a few more resources in his blog post — react-spring-visualizer.com is particularly neat.
React Spring exposes a useSpring
hook that returns a style object for the element you want to animate. Its config accepts fields like tension
and friction
that mirror those real world properties.
I’m using React state to manage mouse position and whether the light is being dragged or has been clicked. Event listeners for mousedown
, mousemove
, mouseup
, and keyup
capture user interaction to trigger theme updates and persistence.
I could have achieved a similar drag ’n release effect with vanilla CSS transitions, but a personal website feels like the right place to experiment and indulge. @react-spring/web
adds about 2.6MB / 51KB minified (20KB gzip). By comparison, react-dom
and react
come in at 4.9MB / 141KB minified (45KB gzip). I’d be curious to benchmark my page load speed with and without React Spring (or any package that adds material weight, really). Something to save for another Sunday morning...
I am using CSS transitions for a smooth fade between day and night. The SVG Edison bulb, which I sketched on paper then created in Figma, also transitions fill
color between theme states. The mountain silhouettes in the footer also fade fill color as you turn off the lights (easiest to see on the homepage). And finally, my favicon is dynamic based on your OS-level theme preference, which is surprisingly easy to add with a media
attribute.
The click of the switch
My skeuomorphic light bulb wouldn’t be complete without the satisfying click of the switch. Toggle below to enable audio then turn the lights on/off!
I’m using an <audio>
tag with a React ref
to rig up sound.
For the actual audio clip, I spent 5 or 10 minutes searching for a nice free sound online before realizing I could just create a recording myself. So the sound you hear is a phone recording of me clicking the switch on my actual Edison bulb!
I disable audio by default and bury the option to enable audio within this Show ’n tell because I generally find sound on websites jarring and distracting. In this case, though, hearing the little sound makes me happy.
Getting accessibilty right
There are a few elements of the component design important for accessibility. For starters, using semantic HTML elements like button
ensures the light bulb controls will be accessible via keyboard. You can reach the switch by tabbing and turn the lights on and off by pressing Enter or Space. I also added an aria-label
attribute to the button and a visually hidden text label for screen reader users with the @radix-ui/react-visually-hidden
utility. I considered whether exposing a theme to screen reader users would even be a useful feature, and I decided it’s best to keep the option open. Any feedback on this is welcome!
Front of the frontend
I fell into the classic developer trope of implementing dark mode before I’d published a single piece of writing, so documenting it felt like a good first step. I do love spending the odd Saturday working on front-of-the-frontend stuff like this, and personally I’m an early bird who much prefers light mode for most everything outside my code editor. Maintaining two themes adds some overhead, but I’m happy to do it for the night owls.