 Welcome to another episode of GUI Challenges where I build interfaces my way and then I challenge you to do your way because with our creative minds combined we are going to find multiple ways to solve these interfaces and expand the diversity of our skills. Today we are building a theme switcher that can toggle your sight between light and dark. Ooh! Can you win that intro? First, a tour. Let's look at our component that we've made here. It's tiny because usually you have these in the top upper right corner sitting somewhere conveniently in a global space for users to toggle and let's one of our first things here. Let's open up DevTools. Let's bump up the size on this. I'm going to go to the button. I have a custom property here called size and let's just bump that up. I'm holding shift and pushing up on my arrow keyboard and boom! We have a ginormous one so now we can see all of the tiny little details that are going into this. So as you can see it has a light and a dark theme. There's a hover feature. Remember, hover should always increase the contrast of something. I think that's really critical. We have a title on there in case something is focusing this and wants to know what its operations are and of course you know we're toggling attributes that are going to make this aware to a screen reader about whether or not it's in a light or a dark state. Let's test out some of our different alt versions that we have our little variants. So we have a light version and we have a dark version and notice how as we toggle this value in the browser it's changing in the page here and we're going to go over the JavaScript that's listening for that and I don't think that's necessarily standard for most light and dark theme switchers to watch the system preference if it's changing. This was a stylistic choice of mine that if my computer has just turned itself to a dark theme or if I explicitly changed it to a dark theme I would like to see the light theme here change. Now that might not be the case for all your users. They might want this to stay. Now it does persist so like if I have this at no emulation and I switch to light theme and I refresh it's still going to be in the light theme. If I go to the dark theme and I refresh it's still in the dark theme. So I'm writing to local storage what your choice is but I am allowing this global feature here to toggle it. Let's go back to a nice large size boopy boopy boopy boop. Okay back to rendering and let's check out what it's like if we prefer reduced motion prefer reduced motion we are just going to instant toggle between these. I toyed with many different crossfades and different animations here and all in all I felt that it just should be instant. I mean it was instantly changing in the page already so why don't I just make this instantly change and so we have in this tiny little component a lot of little things to manage. We have to manage the current color set and we have to manage whether or not there's motion or no motion and it turns out that these things create a little matrix of state that we have to handle and it's no shortage of complexity. So let's go back to no emulation and go back to our elements and so now that we've kind of seen a general overview like if I tab in here and hit spacebar you know I'm going to get the same toggle behavior that I would get if I was clicking it. Let's look at how this is working because while the component is very tiny it has a lot of functionality and it starts with the page load and how we start loading the page and so before we go any further though we have to check out the debugging corner we have to see it working at all the different browsers don't we here's Firefox here if I zoom in command plus command plus command plus you can see it's working very nice nice and smooth here it is on iPad changing the whole color scheme here's Safari iOS Safari nice Android toggling animating Chrome toggling and animating looking good now it's time to go look at the code and we're going to do this one HTML first that's right we are going to start in the HTML and the reason is is we're toggling an attribute on the HTML document so that all the styles and all the elements in the page can know about the theme that the user is desiring here whether it's light or dark and so we have to indicate in our document now when the document is written or as I wrote it it has none and so it's going to be light by default what we do though is load a script and notice the script is here about as high as it can go up in the loading waterfall we want this to load right away because theme toggle is going to go look at the current local storage state it's going to look at the user preference state it's going to decide what attribute to add into the HTML document here and once that HTML attribute gets added all the appropriate styles for the page can load and we want to do this as early as possible so that we don't see a flash of white or a flash of dark when we're in a different theme so it's kind of critical that this gets put up here notice there's no attribute here saying defer or async it's not a type module we need this script to load synchronously we need it to read what's in here it's a small script and apply those attributes to observe this let's go back to chrome go to our elements tab i'm gonna find the html tag since this is where we're setting the attribute i'm gonna right click i'm gonna say break on attribute modification we want to know when something has changed this and i can reload the page and it takes me to the line of java script in the theme toggle file that is changing the value and then i can go look at the call stack so look window.load so the script loaded asynchronously well it loaded it asynchronously but it's gonna execute all the things inside of it synchronously we'll have a window on load so window on load has fired and now we're reflecting a preference and when we reflect this preference we're looking for the document first eminent child which you can see here is the html tag we're setting the attribute to data theme to the theme dot value and the theme dot value is something it's just some internal state to the script so that is the initial loading of the page now things continue to happen so if i hit play we can now go to the document and see that data theme dark is there and if i click again we can see that reflecting the preference it's another place that we're going to set that so this is where the attributes being set this is how we can trace that down and so now that we know that the page is loading we're synchronously loading a script we're synchronously setting an attribute so that our first paint is nice and clear we can go check out the rest of the html and work our way from like top to bottom of how this component which looks so tiny is packing such a punch so i'm going to play through that go back to the elements break on nothing so now we're no longer going to stop whenever that attribute has changed and it looks like we need to go increase our button size again up to do something nice and big 62 awesome switch to dark switch to light awesome we'll go back to the html and we can see it we have a some styles loading afterwards so these are going to be loaded synchronously also because it needs to have these in order to get the page rendered and then we have a button so our button has a class theme toggle we have an idea of theme toggle and we have a title that says toggles light and dark with an aria label set to auto because when the page loads this is set to auto we can also right click our button say break on attribute modifications and click it and see that just after we're reflecting the preference on the html tag we are reflecting the preference on the actual theme toggle button itself kind of neat right i love being able to set element break points really cool stuff okay back to our html we have a button that's what's giving it all those um you know focus highlights and it's just a very interactive element we got a lot of good stuff for free we did sort of switch it into a switch here we're we're giving it this aria label and here let's even look over here what's the aria label say aria label light right and if i play through my debugger break point and i click again aria label light aria label dark so we have the data theme and the aria label in sync being set from the same spot in our javascript and again we're going to go over that here in just a bit but let's look at more of the html inside of our button is where things kind of get really fun with svg right okay so here's our svg tag right here it has a class called sun and moon it's aria hidden true because this is presentational the state of this is actually stashed in the button above and we have a width and a height says to 24 this is just some defaults when it loads same thing with the view box so here we have a mask and we'll talk about that in a second just know that that's the moon and we have a circle that represents the sun and we have um some sun beams with a bunch of lines in here now notice the current color usage and this is just so in case no styles load this button still looks pretty good it's very usable um and it again will have the attributes indicating what its current state is but if we look closely here so here's the sun this center element let's twirl open our svg let's pull down our styles here so we can kind of look at what's in here here's our circle sun notice it's got a cx and cy and a radius of six so we're sort of centering it inside of this 24 by 24 view box giving it a radius of six saying there's a mask called moon mask and there's its color here's our sun beams we can twirl through each of these here's all of our lines very cool and they're all positioned pretty particularly now i got this icon from feather icons and so i just took the um svg right out of it i did end up doing some modifications in order to make the mask work but we'll get into that here in just a second and actually let's go look at the mask the mask here is called moon but it has two items inside in order to make a mask you need black and white and so here's our rect that fills the entire viewport space so it's a zero zero width and height filling the space current color is set to white to fill and then the mask itself and notice how you can see it's pushed off to the side and that's when we click we just moved the mask in and when we move the mask in we cut off the sun shape because the sun shape is pointing to that mask and we um since we've also scaled up this circle see scale 1.75 that's where some maybe some of the mismatch is coming here with dev tools and we go back and all we're doing to go from a sun so we really we have one icon it's a sun icon that we with a little bit of css tweak into a moon by almost sort of um eclipsing it with a mask and it's just a really cool effect and that is the gist of the effect here and we can go dive into some more of the css and the javascript now that you know that that's the operations here and we can even go into the animations here let's click and click and if we pop into here and grab our little time handle we can see in slow motion what's happening so there's our our eclipses moving out so that's our mask we're moving out it's one of the first things that happened in that animation next the sunbeams are rotating and they're transitioning in with their opacity so you can see that here and look at their different curves this the sunbeams rotating have this bounce effect see how they kind of overshoot a little bit and then come back that's a nice easing function that came from open props we have an opacity just like a nice normal easing and then the transform on the scale on the sun is also bouncing a little bit in the same way that the um little sunbeams are going so kind of cool it looks like there's a little bit of a bug here and scrubbing this that like it's resetting this position probably because it's current state but anyway i think you get a nice visual but like how that's happening and what the timing is so all three of these are taking the same amount of time and we're moving the circle out even faster now if we look at this one so this is as we transition from the sun to the moon we're going to scale up the sun we're going to transform and fade out the sunbeams notice how they go out some of the soonest and then as soon as that scale animation is approaching its finish we start moving in our eclipsing moon and then we get our effect i just love when you can see things in slow mo we can even just uh here so pause or play pause or play let's hit 10 on this and um play our animation once that time ahead has reached the end look at that shoo little bounce comes back back from whence it came a nice little eclipsing all right let's set that back to 100 we are ready to dive into the rest of the details here is index.css where i'm importing theme toggle.css and i'm setting a custom media variable called motion okay so let's go look at theme toggle.css here in theme toggle.css i'm importing sun and moon so i've separated the theme toggle button so here if i just collapse this everything's kind of stuck right here in this theme toggle but the sun and moon css has its own file i just felt it was nice to break it out so if we review this button first theme toggle we set a size that's what we saw i was editing nice and easy to change the size we even used this down here at the bottom look we say if there's no hover ability on this device it's visiting how about we bump up the size of that sun and moon so it's a little bit easier for a stylus or a finger to touch kind of cool little effect from using custom properties then the parent element here so this theme toggle button defines the fill colors and the hover colors so here's a nice like bluish dark black so because we're low on the lightness and we're in the sort of blue range here and then we fill hover we want to increase the contrast so we're going to make it even darker this must be oh this is the light theme so this is like a pretty dark gray and this is a super dark gray and then let's see in our data theme dark yep so here we're using at nest this is allowing us to specify a new selector so the selector begins with data theme dark and then brings in our dot theme toggle here so it's data theme dark dot theme toggle and then i can change the icon fill and the icon fill hover the reason we're using the attribute here to toggle our colors is because we're stashing the preference of the user's operating system or their client side choice into a variable which we then put on the html document so we're no longer using the media queries to watch for the switching we're watching this attribute and at nest makes it really easy to keep that context here inside of my initial selector block for this theme toggle and i'm setting this to a pretty light gray 70% lightness and then i'm bumping it all the way up to 90% on hover and that's here you can see it it's like a nice gray and then it's almost white it's a nice gray it's almost white so we're making sure that we're passing contrast in all those states but always again when you're hovering or interacting with something make it easier to see make it easier to find don't push it away and make it look like it's disappearing or becoming disabled okay next up we've got some of the generic styles for this button we've got a background set to none we're pretty much stripping everything off of ever seeing border background none padding none we're setting the size to that custom property aspect ratio to one that's going to give us a perfect square in case this wasn't good enough here the inline size and block size and we're setting the border radius to 50% and whenever that's used with a square you get a nice circle i'm setting a couple of styles here to help mouse users so we have a cursor touch cursor pointer so a mouse user will see that this is an interactive button they'll get the little hand we have touch action manipulation which in case you don't have other attributes on your document or whatever this can make your tap feel much faster as this button is not going to be accepting gestures you're not going to pinch in on this button or flick or fling on this button so we can turn that to manipulation and it means the interactions with this won't be waiting for double taps and stuff like that they can fire much faster super rad then we have web kit tab highlight color transparent and that's the one safari when we tap we're not going to see the sort of semi transparent overlay that's trying to help us understand that you interacted with something um we are doing that work ourselves so i disable it here by setting that color to transparent then i have an outline offset to five pixels i just love doing that because then when you tab into this uh here tab in you get a nice offset of that outline it's not going to hug and shrink wrap it's going to give it a little bit of breathing room then we target the nested svg inside of here and we say hey fill my space and make sure your stroke line caps are round i love this effect it just makes everything look more rounded in here if we go back to here we can see that these are rounded they're rounded at the tops and the bottoms that's really where that's becoming important and that is it for the theme toggle which leaves us to sun and moon the sun and moon imports from open props just the easings and those easings are what make it really easy for us to do the bounce and stuff and we'll see that in a second so here's our sun and moon selector so this is going again we're going to put all of our styles inside of this selector with nesting and that's going to help us keep the scope and study here and the first thing that we do is we target the moon the sun and the sun beams and we say your transform origin should be center when i scale you and i move you i want you to do that from the center or rotate you right those um those sun beams need to rotate from the center then we've got here if it's the sun and the moon so we're selecting sun and moon and one nice little selector with is we're setting the fill to the icon fill and we're at nesting with theme toggle is being hovered or focused find this sun and moon and give them an updated fill to the icon fill hover kind of a fun little selector and operation there and we have the sun beams now the sun beams aren't going to use a fill they're going to use a stroke so we set the sun beam stroke to icon fill same that we were doing up above with the fill we set the stroke width to two pixels and just like we did above this time though we're going to set the stroke to icon fill hover so that's what's handling that as the parent is managing these values and the child has the option to like use these in its uh display so kind of cool there's how the sun beams are being colored and that's the light theme so notice there's no um data theme dark or data theme light here so the default styles for this toggle button are going to be light and we're going to nest right we have at nest again here's our new selector that's then going to pull in the context and the context is sun and moon so data theme sun and moon and the sun so if this is the dark theme the sun needs to be scaled up we saw that in our design we need the sun beams to be opacity zero right it's the moon state so we don't want sun beams and the moon it's circle mask needs to be transformed so we're going to transform and we're going to translate that into position so that's going to bring it into the space then that mask will then mask over that sun shape and that's what gives us that effect here i do have this ad supports cx one this is checking to see if the browser can animate an actual um position of the like svg shape itself and if it can i'm not going to transform anymore i'm going to use the um cx position and i found in chrome i was getting some better performance here so there's some interesting performance implications of transitioning a mask in and out and i was able to kind of pacify and make all browsers high perform it by using this little trick here i bet there's a better way to do that all right let's toggle this down so that's just the dark style so that was essentially how we converted from a sun to a moon and the last step we have is to handle motion and so if motion is okay we're going to target the sun and say hey transition your transform over half a second and use this elastic easing and that's what gives it that bounce and that overshot that when it kind of comes back then we target the sun beams and we say you should transition your transform over half of a second using the elastic four and your opacity over half a second using ease three so if motion is okay and we're in the light theme essentially here still um we're going to be transitioning these values and here's our moon so if the moon and its mask we're going to transition its transform unless we're trying to transition the cx value at which we'll transition the cx value here next is dark theme so again just from like top to bottom really quick we've got our light theme our dark theme our light theme motion okay and then our dark theme motion okay so if this is the dark theme and we're animating here's our sun we want to transform the scale so we're going to scale boost it up we're going to transition the timing function with ease three and we're going to transition over quarter of a second our sun beams are going to rotate and they're going to transition over 0.15 seconds and our circle is going to transition with a delay uh over half a second into its new location so we were able to kind of section out the different states that this needed to be in using nesting and using a nice little controlled set of styles to really make the light and the dark and the light and the dark motion and no motion combinations pretty easy to manage okay hopefully you're like i get it it was a sun and then you animated a mask in and it made it a moon but there's a whole bunch of JavaScript we have to go over to make this actually work so instead of it just toggling visibly we need its change event and its click events and the page load and all these other things to feed into the value that the page saves so that the user has a very very transparent experience so let's dive into that here in theme toggle j s the first value i stash is a storage key so this is in case anything is getting or setting from the local storage this is our little namespace i'm just called it theme preference and we have an on click function that we're stashing here on click is just going to be flipping the current value the theme dot value which is something we're going to be stashing internally down here here's const theme value we're going to set that value equal to well the opposite of whatever it is now so the theme dot value if it's light we're going to set it to dark otherwise we'll set it to light because that must have been dark and then we'll set the preference and we'll get into that in a second so on click we're really just flipping the current value and then calling a function to set preference we have a get color preference function and this says if the local storage has this key in it then return the value of it so if it's there then retrieve it and return it from this function otherwise the window should go look into see it basically we're using javascript to make a media query and we're saying hey what's the current color preference is it dark or is it light well really what we're asking is hey is the current theme that they prefer dark and this comes back as true or false and if that's true we just explicitly return dark and if not we return light so instead of returning whether or not this matches this function is again it's returning the current preference so whether or not that preference is stashed in local storage or we're getting it for the first time from the user's preference here this is where we're getting those values from so this function is returning light or dark then we have set preference this is going to go to local storage it's going to set an item use our storage key and set it to the theme dot value and that's going to reflect that preference and we saw that reflect preference function earlier which was setting the like the attribute value on our html element and we have it setting it on the actual button itself so that screen readers can know the current state of the light and dark setting here is our theme that we're setting this is our just our object with a value in it and we call reflect preference right off the bat so when the script loads right we're loading this synchronously at page load it's going to put all these functions into memory stash this into memory it's going to call get color preference which is going to go initially grab something here it's going to look again in the local storage or the user's preference come back and set that as the value and we're going to reflect right away and we're doing this early so no page flashes css is going to be made aware right that html attribute will be on the document as soon as that css file loads and it can go on its first paint effectively reflect preference we looked at that already so let's look at window dot on load so window dot on load sets on load so screen readers can see the latest value right when we reflect this preference and we're doing it that early in the html document way up here and this is essentially all that's been read so far by the browser it doesn't know that this button even exists so even though we reflected the preference there was no button there to be reflected on only the document so we early tell the document what to be but we have to wait till on load so that we know that the button is there to go reflect the preference again and set it on the button itself now this script can find and listen for clicks on the control and so document query selector here's our theme toggle add event listener click on click so it's going to call our click function which toggles it and last down here hiding we're going to sync with the system changes so we're going to say window dot match media we're looking to see if it's light or dark we're going to add a listener for whether or not it's dark when it changes we want to know is dark or not this is the whole query is only testing to see if it's changed and if it's dark so we want to know if it's dark the theme value if it's dark then set it to dark else set it to light so similar to how we were just returning light and dark from our preference query up above we're going to do it here in this listener and then we're going to set the preference the set of preference is here we set it to local storage we reflect the preference up into our html document and down into our button and that is the extent of this theme toggle I thought theme toggles were easy but no they are not they're jam packed with all sorts of options all sorts of things to be worried about how are you building these are you setting an attribute on your html document are you just loading different script files with javascript there's so many different ways to do this I'm really excited to see how you're doing it I hope you liked this animation there's lots of little things in here I know we looked at the you know the these and and kind of checked them out but you can see how different easings and different durations and like a delay or not can really change the way that something feels like this delay on this mask coming in it's really critical oh look I think it's because we're here we'll just clear out of there yeah it's really critical that that delay on the eclipse comes in later it just a lot of little things go into making this feel that smooth and that beautiful so thanks for watching this gooey challenge I hope you enjoyed this one and stay tuned for the next one I look forward to all your submissions and see you later y'all