{"id":4412,"date":"2024-11-18T15:14:58","date_gmt":"2024-11-18T20:14:58","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=4412"},"modified":"2024-11-18T15:14:59","modified_gmt":"2024-11-18T20:14:59","slug":"no-fuss-light-dark-modes","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/no-fuss-light-dark-modes\/","title":{"rendered":"No Fuss Light\/Dark Modes"},"content":{"rendered":"\n<p>There was some user feedback this site should have a light mode instead of all-dark-all-the-time. The theme of this site is simple enough that some quick design tweaks is all it took.<\/p>\n\n\n\n<p>But here&#8217;s the thing: it just relies on your system preference. <\/p>\n\n\n\n<p>(Or whatever is controlling what <code>prefers-color-scheme<\/code> is returning in your browser. I use the browser Arc sometimes and it&#8217;s Light\/Dark modes override what is set in my System Preferences. It <a href=\"https:\/\/frontendmasters.com\/blog\/why-is-this-thing-in-dark-mode\/\">can get confusing<\/a>.)<\/p>\n\n\n\n<p>It&#8217;s more on-trend when offering color modes to offer users a <strong>toggle.<\/strong> That way users can easily choose between which they prefer without ever leaving your site. And they might have a preference that is the <em>opposite<\/em> of what their overall system preference is. Those are perfectly fair and legitment things.<\/p>\n\n\n\n<p>Those things also complicate the work. <\/p>\n\n\n\n<p>I think it&#8217;s also perfectly fair to make development choices that are purposefully <em>un<\/em>complicated. <\/p>\n\n\n\n<p>In this case, choosing to support light and dark modes <em>entirely within CSS alone<\/em> was the uncomplicated choice. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Basics<\/h2>\n\n\n\n<p>It really is just this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">html<\/span> {\n  <span class=\"hljs-attribute\">--brand-red<\/span>: <span class=\"hljs-built_in\">oklch<\/span>(<span class=\"hljs-number\">0.67<\/span> <span class=\"hljs-number\">0.24<\/span> <span class=\"hljs-number\">27.98<\/span>);\n\n  <span class=\"hljs-attribute\">--bg<\/span>: black;\n  <span class=\"hljs-attribute\">--text<\/span>: <span class=\"hljs-number\">#ffdbdb<\/span>;\n\n  <span class=\"hljs-attribute\">--link-color<\/span>: <span class=\"hljs-number\">#4ac6ff<\/span>;\n  <span class=\"hljs-attribute\">--link-color-hover<\/span>: <span class=\"hljs-number\">#9ce0ff<\/span>;\n  <span class=\"hljs-attribute\">--bright-color<\/span>: white;\n  <span class=\"hljs-attribute\">--faded-color<\/span>: <span class=\"hljs-number\">#373232<\/span>;\n}\n\n<span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">prefers-color-scheme:<\/span> light) {\n  <span class=\"hljs-selector-tag\">html<\/span> {\n    <span class=\"hljs-attribute\">--bg<\/span>: white;\n    <span class=\"hljs-attribute\">--text<\/span>: <span class=\"hljs-number\">#323232<\/span>;\n\n    <span class=\"hljs-attribute\">--link-color<\/span>: <span class=\"hljs-number\">#068dcb<\/span>;\n    <span class=\"hljs-attribute\">--link-color-hover<\/span>: <span class=\"hljs-number\">#67cfff<\/span>;\n    <span class=\"hljs-attribute\">--bright-color<\/span>: black;\n    <span class=\"hljs-attribute\">--faded-color<\/span>: <span class=\"hljs-number\">#dedede<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then steadfastly use those color variables everywhere any color is set.<\/p>\n\n\n\n<p>The red stays the same across both. Fortunately red is fine with that. <\/p>\n\n\n\n<p>The main point of modes is that <em>most<\/em> of the color should be dominantly dark or dominantly light, which is <em>mostly<\/em> about backgrounds. So the <code>--bg<\/code> background does the work there and the <code>--text<\/code> variable is an accessible color that sits on top of that background. <\/p>\n\n\n\n<p>But it&#8217;s never <em>quite that easy<\/em>. You always need to need a couple of more colors, even on simple sites. So here I&#8217;m setting up variables for links and a couple of variations. <\/p>\n\n\n\n<p>Purposefully simple.<\/p>\n\n\n\n<p>I kinda like approach of just changing same-named <code>--custom-properties<\/code> myself, but there are alternatives. For instance you could use named colors (e.g. <code>--my-gray-8<\/code>) then use the <a href=\"https:\/\/frontendmasters.com\/blog\/dark-and-light\/\">now well-supported <code>light-dark()<\/code> function<\/a> to do something like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.card<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">light-dark<\/span>(var(--my-gray-<span class=\"hljs-number\">4<\/span>), <span class=\"hljs-built_in\">var<\/span>(--my-gray-<span class=\"hljs-number\">9<\/span>));\n  <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-built_in\">light-dark<\/span>(var(--my-gray-<span class=\"hljs-number\">9<\/span>), <span class=\"hljs-built_in\">var<\/span>(--my-gray-<span class=\"hljs-number\">1<\/span>))\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Why is offering a site-level toggle so challenging?<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The site level choice needs to override any other choice, so it means you can&#8217;t leverage <code>@media<\/code> very cleanly. But you still need to use <code>@media<\/code> for the default if there isn&#8217;t a choice, so you can&#8217;t back away from it entirely. <\/li>\n\n\n\n<li>You have to persist the choice, otherwise simply refreshing the browser could wipe away the choice, which is pretty weak sauce. Persisting data means at a minimum using <code>localStorage<\/code> or cookies, but you&#8217;d probably want to do better than that.<\/li>\n\n\n\n<li>The user choice can be different on the site than what their system or browser-level choice might be, so you need to load what that choice is before you render anything. Otherwise you risk <a href=\"https:\/\/css-tricks.com\/flash-of-inaccurate-color-theme-fart\/\">Flash of inAccurate coloR Theme&nbsp;(FART)<\/a> which is incredibly awkward. <\/li>\n<\/ul>\n\n\n\n<p>I&#8217;d say it&#8217;s still worth doing if you&#8217;re working on a &#8220;big&#8221; site where you expect quite a bit of time-on-site from your users. You can also do something like I&#8217;ve done above <em>as a first step<\/em> and then move onto a toggle approach. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Effect<\/h2>\n\n\n\n<p>I swear the change is much smoother than this (no thanks to <code>transition<\/code> or anything, macOS just makes it super smooth somehow). But when I was recording this video it wanted to be a more more abrupt \ud83e\udd37\u200d\u2640\ufe0f. <\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player\" style=\"\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='347' src='https:\/\/videopress.com\/embed\/eEQhTrQM?cover=1&amp;autoPlay=0&amp;controls=1&amp;loop=0&amp;muted=0&amp;persistVolume=1&amp;playsinline=0&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent=\"true\" allow='clipboard-write'><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1725245713'><\/script><\/div>\n\t\t\t\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p>This was based on user-feedback, remember? Well one of those users noticed immediately and thanked Marc because it&#8217;s a better experience for them.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"813\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/IMG_0320.jpg?resize=1024%2C813&#038;ssl=1\" alt=\"\" class=\"wp-image-4458\" style=\"width:382px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/IMG_0320.jpg?resize=1024%2C813&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/IMG_0320.jpg?resize=300%2C238&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/IMG_0320.jpg?resize=768%2C610&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/IMG_0320.jpg?w=1320&amp;ssl=1 1320w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>One approach to color modes it to do it all in CSS with prefers-color-scheme media queries and\/or the light-dark() function. A user toggle can be a future improvement.<\/p>\n","protected":false},"author":1,"featured_media":4524,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[81,7,157],"class_list":["post-4412","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-color","tag-css","tag-dark-theme"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/11\/pexels-photo-5138933.jpeg?fit=1733%2C1300&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4412","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=4412"}],"version-history":[{"count":9,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4412\/revisions"}],"predecessor-version":[{"id":4526,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4412\/revisions\/4526"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/4524"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=4412"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=4412"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=4412"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}