Tag: Mode

A Complete Guide to Dark Mode on the Web

Dark mode has gained a lot of traction recently. Like Apple, for instance, has added dark mode to its iOS and MacOS operating systems. Windows and Google have done the same. 

DuckDuckGo’s light and dark themes

Let’s get into dark mode in the context of websites. We’ll delve into different options and approaches to implementing a dark mode design and the technical considerations they entail. We’ll also touch upon some design tips along the way.


Toggling Themes

The typical scenario is that you already have a light theme for your site, and you’re interested in making a darker counterpart. Or, even if you’re starting from scratch, you’ll have both themes: light and dark. One theme should be defined as the default that users get on first visit, which is the light theme in most cases (though we can let the user’s browser make that choice for us, as we’ll see). There also should be a way to switch to the other theme (which can be done automatically, as we’ll also see) — as in, the user clicks a button and the color theme changes.

There several approaches to go about doing this:

Using a Body Class

The trick here is to swap out a class that can be a hook for changing a style anywhere on the page.

<body class="dark-theme || light-theme">

Here’s a script for a button that will toggle that class, for example:

// Select the button const btn = document.querySelector('.btn-toggle');  // Listen for a click on the button btn.addEventListener('click', function() {   // Then toggle (add/remove) the .dark-theme class to the body   document.body.classList.toggle('dark-theme');   })

Here’s how we can use that idea:

<body>   <button class="btn-toggle">Toggle Dark Mode</button>   <h1>Hey there! This is just a title</h2>   <p>I am just a boring text, existing here solely for the purpose of this demo</p>   <p>And I am just another one like the one above me, because two is better than having only one</p>   <a href="#">I am a link, don't click me!</a> </body>

The general idea of this approach is to style things up as we normally would, call that our “default” mode, then create a complete set of color styles using a class set on the <body>  element we can use as a “dark” mode.

Let’s say our default is a light color scheme. All of those “light” styles are written exactly the same way you normally write CSS. Given our HTML, let’s apply some global styling to the body and to links.

body {   color: #222;   background: #fff; } a {   color: #0033cc; }

Good good. We have dark text (#222) and dark links (#0033cc) on a light background (#fff). Our “default” theme is off to a solid start.

Now let’s redefine those property values, this time set on a different body class:

body {   color: #222;   background: #fff; } a {   color: #0033cc; } 
 /* Dark Mode styles */ body.dark-theme {   color: #eee;   background: #121212; } body.dark-theme a {   color: #809fff; }

Dark theme styles will be descendants of the same parent class — which is .dark-theme in this example — which we’ve applied to the <body> tag.

How do we “switch” body classes to access the dark styles? We can use JavaScript! We’ll select the button class (.btn-toggle), add a listener for when it’s clicked, then add the dark theme class (.dark-theme) to the body element’s class list. That effectively overrides all of the “light” colors we set, thanks to the cascade and specificity. 

Here’s the complete code working in action. Click the toggle button to toggle in and out of dark mode.

Using Separate Stylesheets

Rather than keeping all the styles together in one stylesheet, we could instead toggle between stylesheets for each theme. This assumes you have full stylesheets ready to go.

For example, a default light theme like light-theme.css:

/* light-theme.css */ 
 body {   color: #222;   background: #fff; } a {   color: #0033cc; }

Then we create styles for the dark theme and save them in a separate stylesheet we’re calling dark-theme.css.

/* dark-theme.css */ 
 body {   color: #eee;   background: #121212; } body a {   color: #809fff; }

This gives us two separate stylesheets — one for each theme — we can link up in the HTML <head> section. Let’s link up the light styles first since we’re calling those the default.

<!DOCTYPE html> <html lang="en"> <head>   <!-- Light theme stylesheet -->   <link href="light-theme.css" rel="stylesheet" id="theme-link"> </head> 
 <!-- etc. --> 
 </html>

We are using a #theme-link ID that we can select with JavaScript to, again, toggle between light and dark mode. Only this time, we’re toggling files instead of classes.

// Select the button const btn = document.querySelector(".btn-toggle"); // Select the stylesheet <link> const theme = document.querySelector("#theme-link");  // Listen for a click on the button btn.addEventListener("click", function() {   // If the current URL contains "ligh-theme.css"   if (theme.getAttribute("href") == "light-theme.css") {     // ... then switch it to "dark-theme.css"     theme.href = "dark-theme.css";   // Otherwise...   } else {     // ... switch it to "light-theme.css"     theme.href = "light-theme.css";   } });

Using Custom Properties

We can also leverage the power of CSS custom properties to create a dark theme! It helps us avoid having to write separate style rulesets for each theme, making it a lot faster to write styles and a lot easier to make changes to a theme if we need to.

We still might choose to swap a body class, and use that class to re-set custom properties:

// Select the button const btn = document.querySelector(".btn-toggle"); 
 // Listen for a click on the button btn.addEventListener("click", function() {   // Then toggle (add/remove) the .dark-theme class to the body   document.body.classList.toggle("dark-theme"); });

First, let’s define the default light color values as custom properties on the body element:

body {   --text-color: #222;   --bkg-color: #fff;   --anchor-color: #0033cc; }

Now we can redefine those values on a .dark-theme body class just like we did in the first method:

body.dark-theme {   --text-color: #eee;   --bkg-color: #121212;   --anchor-color: #809fff; }

Here are our rulesets for the body and link elements using custom properties:

body {   color: var(--text-color);   background: var(--bkg-color); } a {   color: var(--anchor-color); }

We could just as well have defined our custom properties inside the document :root. That’s totally legit and even common practice. In that case, all the default theme styles definitions would go inside :root { } and all of the dark theme properties go inside :root.dark-mode { }.

Using Server-Side Scripts

If we’re already working with a server-side language, say PHP, then we can use it instead of JavaScript. This is a great approach if you prefer working directly in the markup.

<?php $ themeClass = ''; if (isset($ _GET['theme']) && $ _GET['theme'] == 'dark') {   $ themeClass = 'dark-theme'; } 
 $ themeToggle = ($ themeClass == 'dark-theme') ? 'light' : 'dark'; ?> <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>">   <a href="?theme=<?php echo $ themeToggle; ?>">Toggle Dark Mode</a>   <!-- etc. --> </body> </html>

We can have the user send a GET or POST request. Then, we let our code (PHP in this case) apply the appropriate body class when the page is reloaded. I am using a GET request (URL params) for the purpose of this demonstration.

And, yes, we can swap stylesheets just like we did in the second method.

<?php $ themeStyleSheet = 'light-theme.css'; if (isset($ _GET['theme']) && $ _GET['theme'] == 'dark') {   $ themeStyleSheet = 'dark-theme.css'; } 
 $ themeToggle = ($ themeStyleSheet == 'dark-theme.css') ? 'light' : 'dark'; ?> <!DOCTYPE html> <html lang="en"> <head>   <!-- etc. -->   <link href="<?php echo $ themeStyleSheet; ?>" rel="stylesheet"> </head> 
 <body>   <a href="?theme=<?php echo $ themeToggle; ?>">Toggle Dark Mode</a>   <!-- etc. --> </body> </html>

This method has an obvious downside: the page needs to be refreshed for the toggle to take place. But a server-side solution like this is useful in persisting the user’s theme choice across page reloads, as we will see later.


Which method should you choose?

The “right” method comes down to the requirements of your project. If you are doing a large project, for example, you might go with CSS properties to help wrangle a large codebase. On the other hand, if your project needs to support legacy browsers, then another approach will need to do instead.

Moreover, there’s nothing saying we can only use one method. Sometimes a combination of methods will be the most effective route. There may even be other possible methods than what we have discussed.


Dark Mode at the Operating System Level

So far, we’ve used a button to toggle between light and dark mode but we can simply let the user’s operating system do that lifting for us. For example, many operating systems let users choose between light and dark themes directly in the system settings.

The “General” settings in MacOS System Preferences

Pure CSS

Details

Fortunately, CSS has a prefers-color-scheme media query which can be used to detect user’s system color scheme preferences. It can have three possible values: no preference, light and dark. Read more about it on MDN.

@media (prefers-color-scheme: dark) {   /* Dark theme styles go here */ } 
 @media (prefers-color-scheme: light) {   /* Light theme styles go here */ }

To use it, we can put the dark theme styles inside the media query.

@media (prefers-color-scheme: dark) {   body {     color: #eee;     background: #121212;   } 
   a {     color: #809fff;   } }

Now, if a user has enabled dark mode from the system settings, they will get the dark mode styles by default. We don’t have to resort to JavaScript or server-side scripts to decide which mode to use. Heck, we don’t even need the button anymore!

JavaScript

Details

We can turn to JavaScript to detect the user’s preferred color scheme. This is a lot like the first method we worked with, only we’re using matchedMedia() to detect the user’s preference.

const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');nnif (prefersDarkScheme.matches) {n  document.body.classList.add('dark-theme');n} else {n  document.body.classList.remove('dark-theme');n}

There is a downside to using JavaScript: there will likely be a quick flash of the light theme as JavaScript is executed after the CSS. That could be misconstrued as a bug.

And, of course, we can swap stylesheets instead like we did in the second method. This time, we link up both stylesheets and use the media query to determine which one is applied.

Overriding OS Settings

We just looked at how to account for a user’s system-wide color scheme preferences. But what if users want to override their system preference for a site? Just because a user prefers dark mode for their OS doesn’t always mean they prefer it on a website. That’s why providing a way to manually override dark mode, despite the system settings, is a good idea.

View Code

Let’s use the CSS custom properties approach to demonstrate how to do this. The idea is to define the custom properties for both themes like we did before, wrap dark styles up in the prefers-color-scheme media query, then define a .light-theme class inside of that we can use to override the dark mode properties, should the user want to toggle between the two modes.

/* Default colors */ body {   --text-color: #222;   --bkg-color: #fff; } /* Dark theme colors */ body.dark-theme {   --text-color: #eee;   --bkg-color: #121212; }  /* Styles for users who prefer dark mode at the OS level */ @media (prefers-color-scheme: dark) {   /* defaults to dark theme */   body {      --text-color: #eee;     --bkg-color: #121212;   }   /* Override dark mode with light mode styles if the user decides to swap */   body.light-theme {     --text-color: #222;     --bkg-color: #fff;   } }

Now we can turn back to our trusty button to toggle between light and dark themes. This way, we’re respecting the OS color preference by default and allowing the user to manually switch themes.

// Listen for a click on the button  btn.addEventListener("click", function() {   // If the OS is set to dark mode...   if (prefersDarkScheme.matches) {     // ...then apply the .light-theme class to override those styles     document.body.classList.toggle("light-theme");     // Otherwise...   } else {     // ...apply the .dark-theme class to override the default light styles     document.body.classList.toggle("dark-theme");   } });

Browser Support

The prefers-color-scheme media query feature enjoys support by major browsers, including Chrome 76+, Firefox 67+, Chrome Android 76+ and Safari 12.5+ (13+ on iOS). It doesn’t support IE and Samsung Internet Browser.

That’s a promising amount of support!. Can I Use estimates 80.85% of user coverage.

Operating systems that currently support dark mode include MacOS (Mojave or later), iOS (13.0+), Windows (10+), and Android (10+).


Storing a User’s Preference

What we’ve looked at so far definitely does what it says in the tin: swap themes based on an OS preference or a button click. This is great, but doesn’t carry over when the user either visits another page on the site or reloads the current page.

We need to save the user’s choice so that it will be applied consistently throughout the site and on subsequent visits. To do that, we can save the user’s choice to the localStorage when the theme is toggled. Cookies are also well-suited for the job.

Let’s look at both approaches.

Using localStorage

We have a script that saves the selected theme to localStorage when the toggle takes place. In other words, when the page is reloaded, the script fetches the choice from localStorage and applies it. JavaScript is often executed after CSS, so this approach is prone to a “flash of incorrect theme” (FOIT).

View Code
// Select the button const btn = document.querySelector(".btn-toggle"); // Select the theme preference from localStorage const currentTheme = localStorage.getItem("theme"); 
 // If the current theme in localStorage is "dark"... if (currentTheme == "dark") {   // ...then use the .dark-theme class   document.body.classList.add("dark-theme"); } 
 // Listen for a click on the button  btn.addEventListener("click", function() {   // Toggle the .dark-theme class on each click   document.body.classList.toggle("dark-theme");      // Let's say the theme is equal to light   let theme = "light";   // If the body contains the .dark-theme class...   if (document.body.classList.contains("dark-theme")) {     // ...then let's make the theme dark     theme = "dark";   }   // Then save the choice in localStorage   localStorage.setItem("theme", theme); });

Using Cookies with PHP

To avoid FLIC, we can use a server-side script like PHP. Instead of saving the user’s theme preference in localStorage, we will create a cookie from JavaScript and save it there. But again, this may only be feasible if you’re already working with a server-side language.

View Code
// Select the button const btn = document.querySelector(".btn-toggle"); 
 // Listen for a click on the button  btn.addEventListener("click", function() {   // Toggle the .dark-theme class on the body   document.body.classList.toggle("dark-theme");      // Let's say the theme is equal to light   let theme = "light";   // If the body contains the .dark-theme class...   if (document.body.classList.contains("dark-theme")) {     // ...then let's make the theme dark     theme = "dark";   }   // Then save the choice in a cookie   document.cookie = "theme=" + theme; });

We can now check for the existence of that cookie and load the appropriate theme by applying the proper class to the <body> tag.

<?php $ themeClass = ''; if (!empty($ _COOKIE['theme']) && $ _COOKIE['theme'] == 'dark') {   $ themeClass = 'dark-theme'; } ?> 
 <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>"> <!-- etc. --> </body> </html>

Here is how to do that using the separate stylesheets method:

<?php $ themeStyleSheet = 'light-theme.css'; if (!empty($ _COOKIE['theme']) && $ _COOKIE['theme'] == 'dark') {   $ themeStyleSheet = 'dark-theme.css'; } ?> 
 <!DOCTYPE html> <html lang="en"> <head>   <!-- etc. -->   <link href="<?php echo $ themeStyleSheet; ?>" rel="stylesheet" id="theme-link"> </head> <!-- etc. -->

If your website has user accounts — like a place to log in and manage profile stuff — that’s also a great place to save theme preferences. Send those to the database where user account details are stored. Then, when the user logs in, fetch the theme from the database and apply it to the page using PHP (or whatever server-side script).

There are various ways to do this. In this example, I am fetching the user’s theme preference from the database and saving it in a session variable at the time of login.

<?php // Login action if (!empty($ _POST['login'])) {   // etc. 
   // If the uuser is authenticated...   if ($ loginSuccess) {     // ... save their theme preference to a session variable     $ _SESSION['user_theme'] = $ userData['theme'];   } } 
 // Pick the session variable first if it's set; otherwise pick the cookie $ themeChoice = $ _SESSION['user_theme'] ?? $ _COOKIE['theme'] ?? null; $ themeClass = ''; if ($ themeChoice == 'dark') {   $ themeClass = 'dark-theme'; } ?> 
 <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>"> <!-- etc. --> </body> </html>

I am using PHP’s null coalesce operator (??) to decide where to pick the theme preference: from the session or from the cookie. If the user is logged in, the value of the session variable is taken instead that of the cookie. And if the user is not logged in or has logged out, the value of cookie is taken.


Handling User Agent Styles

To inform the browser UA stylesheet about the system color scheme preferences and tell it which color schemes are supported in the page, we can use the color-scheme meta tag.

For example, let’s say the page should support both “dark” and “light” themes. We can put both of them as values in the meta tag, separated by spaces. If we only want to support a “light” theme, then we only need to use “light” as the value. This is discussed in a CSSWG GitHub issue, where it was originally proposed.

<meta name="color-scheme" content="dark light">

When this meta tag is added, the browser takes the user’s color scheme preferences into consideration when rendering UA-controlled elements of the page (like a <button>). It renders colors for the root background, form controls, and spell-check features (as well as any other UA-controlled styles) based on the user’s preference.

Source

Although themes are manually styled for the most part (which overrides the UA styles), informing the browser about the supported themes helps to avoid even the slightest chance of a potential FOIT situation. This is true for those occasions where HTML has rendered but CSS is still waiting to load.

We can also set this in CSS:

:root {   color-scheme: light dark; /* both supported */ }
via Jim Nielsen

At the time of writing, the color-scheme property lacks broad browser support, though Safari and Chrome both support it.


Combining all the things!

Let’s combine everything and create a working demo that:

  1. Automatically loads a dark or light theme based on system preferences
  2. Allows the user to manually override their system preference
  3. Maintains the user’s preferred theme on page reloads

Using JavaScript & Local Storage

// Select the button const btn = document.querySelector(".btn-toggle"); // Check for dark mode preference at the OS level const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)"); 
 // Get the user's theme preference from local storage, if it's available const currentTheme = localStorage.getItem("theme"); // If the user's preference in localStorage is dark... if (currentTheme == "dark") {   // ...let's toggle the .dark-theme class on the body   document.body.classList.toggle("dark-mode"); // Otherwise, if the user's preference in localStorage is light... } else if (currentTheme == "light") {   // ...let's toggle the .light-theme class on the body   document.body.classList.toggle("light-mode"); } 
 // Listen for a click on the button  btn.addEventListener("click", function() {   // If the user's OS setting is dark and matches our .dark-mode class...   if (prefersDarkScheme.matches) {     // ...then toggle the light mode class     document.body.classList.toggle("light-mode");     // ...but use .dark-mode if the .light-mode class is already on the body,     var theme = document.body.classList.contains("light-mode") ? "light" : "dark";   } else {     // Otherwise, let's do the same thing, but for .dark-mode     document.body.classList.toggle("dark-mode");     var theme = document.body.classList.contains("dark-mode") ? "dark" : "light";   }   // Finally, let's save the current preference to localStorage to keep using it   localStorage.setItem("theme", theme); });

Using PHP & Cookies

<?php $ themeClass = ''; if (!empty($ _COOKIE['theme'])) {   if ($ _COOKIE['theme'] == 'dark') {     $ themeClass = 'dark-theme';   } else if ($ _COOKIE['theme'] == 'light') {     $ themeClass = 'light-theme';   }   } ?> 
 <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>"> <!-- etc. --> <script>   const btn = document.querySelector(".btn-toggle");   const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");      btn.addEventListener("click", function() {     if (prefersDarkScheme.matches) {       document.body.classList.toggle("light-mode");       var theme = document.body.classList.contains("light-mode") ? "light" : "dark";     } else {       document.body.classList.toggle("dark-mode");       var theme = document.body.classList.contains("dark-mode") ? "dark" : "light";     }     document.cookie = "theme=" + theme;   }); </script> </body> </html>


Design Considerations

I often hear that implementing dark mode is easier than designing one. While I’ll refrain from judgement, let’s look at some considerations for designing a dark theme.

You already know the basic task: swap lighter color values for darker ones and vice versa. But there are some UI elements and enhancements that are more nuanced and require more attention. Let’s take a look at those.

Dark Mode Images

A good rule is to decrease the brightness and contrast of images a bit so that it looks comfortable to the eyes when it’s against a dark background. A super bright image on a super dark background can be jarring and dimming the image reduces some of that heavy contrast.

The CSS filter() function is more than capable of handling this for us:

/* Apply the filter directly on the body tag */ body.dark-theme img {   filter: brightness(.8) contrast(1.2); } 
 /* Or apply it via media query */ @media (prefers-color-scheme: dark) {   img {     filter: brightness(.8) contrast(1.2);   } }

We can do the same sort of thing directly in the markup using the <picture> element to load different versions of an image:

<picture>   <!-- Use this image if the user's OS setting is light or unset -->   <source srcset="photo-light.png" media="(prefers-color-scheme: light) or (prefers-color-scheme: no-preference)">   <!-- Use this image if the user's OS setting is dark -->   <source srcset="photo-dark.png" media="(prefers-color-scheme: dark)"> </picture>

The downside here is that it requires supplying two files where we only have to deal with one when using CSS. This also doesn’t fully account for the user toggling the color theme on the site.

Dark Mode Shadows

Dark mode shadows are tricky. If we simply invert a dark shadow using light colors, then we get this funky thing with a light shadow on a dark background… and it’s not a good look.

It’s possible to use a dark shadow in dark mode, but the background color has to be “light” enough (like a dark gray) to provide enough contrast to actually see the shadow against it.

Use opacity to convey depth, with high opacity regions having a lower depth. That’s to say, elements that have a higher elevation should have a lower opacity than elements that are “closer” in depth to the background.

Different shades of color create different perceptions of “depth”

Dark Mode Typography

The trick here is a lot like images: we’ve gotta balance the contrast. Use too heavy of a font and we get blaring text that’s makes us want to move away from the screen. Use too light of a font and we’ll strain our eyes while inching toward the screen to get a closer look.

The balance is somewhere in the middle. Robin has a nice write-up where he suggests a small bit of CSS that makes a big difference in legibility.

Dark Mode Icons

Icons fall into this “tricky” category because they’re sort of a cross between text and images. If we’re working with SVG icons, though, we can change the fill with CSS. On the other hand, if we’re using font icons, we can simply change the color property instead.

/* SVG icon */ body.dark-theme svg.icon path {   fill: #efefef; } /* Font icon (using Font Awesome as an example) */ body.dark-theme .fa {   color: #efefef; }

A lot of the same design considerations that are true for text, are also generally applicable to icons. For example, we ought to avoid using full white and heavy outlines.

Dark Mode Colors

Pure white text on a pure black background will look jarring. The trick here is to use an off-white for the text and off-black for the background. Material Design Guidelines for example recommends #121212 for the background.

Dark Mode Color Palettes

We’ve seen the difference using off-white and off-black colors makes for text and images. Let’s expand on that a bit with tips on how to develop a full color palette.

Most things boil down to one thing: contrast. That’s why the first tip before settling on any color is to run ideas through a contrast checker to ensure color ratios conform to WCAG’s guidelines for at least a AA rating, which is a contrast ratio of 4.5:1.

That means desaturated colors are our friends when working with a dark mode design. They help prevent overbearingly bright images and still give us plenty of room to create an effective contrast ratio.

Next, remember that accent colors are meant to be enhancements. They’re likely brighter than the dark theme background color, so using them like a primary color or the background color of a large container is just as jarring and hard on the eyes as a bright image or heavy white text.

If contrast is the balance we’re trying to strike, then remember that dark mode is more than blacks and grays. What about dark blue background with pale yellow text? Or dark brown with tan? There’s an entire (and growing) spectrum of color out there and we can leverage any part of it to fuel creativity.

A few examples of colors that are dark without resorting to full-on black:

#232B32

#152028

#202945

Material Design’s guidelines on dark mode is a handy resource on best practices for dark mode design. It’s definitely worth a read for more tips to keep in mind.

Dark Mode in the Wild

YouTube uses the CSS variables technique. They’ve defined all their colors in variables under the html selector while dark mode colors are defined under html:not(.style-scope)[dark]. When dark mode is enabled, YouTube adds a dark="true" attribute to the <html> tag. This is what they use to override the variables defined in the HTML.

YouTube adds dark=true attribute to the <html> when it switches to the dark mode.

In the wild, the CSS custom properties approach seems to be most popular. It’s being used by Dropbox Paper, Slack, and Facebook.

Simplenote uses the class-swapping method where all light style rules are descendants of a .theme-light parent class and all the dark styles fall under a .theme-dark class. When the theme is toggled, the appropriate class is applied to the <body> tag.

Simplenote uses two classes: .light-theme and .dark-theme to style the themes.

Twitter goes the extra mile and offers several themes to choose from: “Default,” “Dim,” and “Lights out.” The “Dim” option employs dark blue for a background color. Compare that to “Lights out” which uses a stark black.

Twitter offers three themes to choose from.

Dark mode or no dark mode? That is the question.

There are perfectly valid reasons on both sides. Some of those reasons even go beyond the scope of user experience and include things like timing, budget and resources.

While being considerate of why you might not want to implement a dark mode, here are reasons why you might want to have one:

  • It’s cool and trendy (although that’s not a reason alone to do it)
  • It enhances accessibility by supporting users who are sensitive to eye strain in starkly bright themes.
  • It allows users to decide the most comfortable way to consume content while providing us a way to maintain control over the look and feel of things. Remember, we want to beat the Reader Mode button!
  • It helps to preserve battery life for devices with OLED screen where brighter colors consume more energy.
  • It’s extremely popular and appears to be going nowhere. It’s possible that your users who prefer a dark mode (like me!) will expect your site to have one. Might as well be ready for it.

The post A Complete Guide to Dark Mode on the Web appeared first on CSS-Tricks.

CSS-Tricks

, , ,

Quick Tips for High Contrast Mode

Sarah Higley has some CSS tricks up her sleeve for dealing with High Contrast Mode on Windows, which I learned is referred to as WHCM.

Here’s the first trick:

[…] if the default CSS outline property doesn’t give you the visual effect you want [in WHCM] for focus states, there’s a very simple fix. Instead of overriding default browser focus styles with outline: none, make it transparent instead: outline 3px solid transparent.

That will essentially do nothing outside of WHCM, but in WHCM, it will be a thick white border, which is a strong, good visual focus style.

Direct Link to ArticlePermalink

The post Quick Tips for High Contrast Mode appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Dark mode and variable fonts

Not so long ago, we wrote about dark mode in CSS and I’ve been thinking about how white text on a black background is pretty much always harder to read than black text on a white background. After thinking about this for a while, I realized that we can fix that problem by making the text thinner in dark mode using variable fonts!

Here’s an example of the problem where I’m using the typeface Yanone Kaffeesatz from Google Fonts. Notice that the section with white text on a black background looks heavier than the section with black text on a white background.

Oddly enough, these two bits of text are actually using the same font-weight value of 400. But to my eye, the white text looks extra bold on a black background.

Stare at this example for a while. This is just how white text looks on a darker background; it’s how our eyes perceive shapes and color. And this might not be a big issue in some cases but reading light text on a dark background is always way more difficult for readers. And if we don’t take care designing text in a dark mode context, then it can feel as if the text is vibrating as we read it.

How do we fix this?

Well, this is where variable fonts come in! We can use a lighter font weight to make the text easier to read whenever dark mode is active:

body {   font-weight: 400; }  @media (prefers-color-scheme: dark) {   body {     font-weight: 350;   } }

Here’s how that looks with this new example:

This is better! The two variants now look a lot more balanced to me.

Again, it’s only a small difference, but all great designs consist of micro adjustments like this. And I reckon that, if you’re already using variable fonts and loading all these weights, then you should definitely adjust the text so it’s easier to read.

This effect is definitely easier to spot if we compare the differences between longer paragraphs of text. Here we go, this time in Literata:

Notice that the text on the right feels bolder, but it just isn’t. It’s simply an optical allusion — both examples above have a font-weight of 500.

So to fix this issue we can do the same as the example above:

body {   font-weight: 500; }  @media (prefers-color-scheme: dark) {   body {     font-weight: 400;   } }

Again, it’s a slight change but it’s important because at these sizes every typographic improvement we make helps the reading experience.

Oh and here’s a quick Google fonts tip!

Google Fonts lets you can add a font to your website by adding a <link> in the <head> of the document, like this:

<head>   <link href="https://fonts.googleapis.com/css2?family=Rosario:wght@515&display=swap" rel="stylesheet">  </head>

That’s using the Rosario typeface and adding a font-weight of 515 — that’s the bit in the code above that says wght@515. Even if this happens to be a variable font, 515 only this font weight that’s downloaded. If we try to do something like this:

body {   font-weight: 400; }

…nothing will happen! In fact, the font won’t load at all. Instead, we need to declare which range of font-weight values we want by doing the following:

<link href="https://fonts.googleapis.com/css2?family=Yanone+Kaffeesatz:wght@300..500&display=swap" rel="stylesheet">

This @300..500 bit in the code above is what tells Google Fonts to download a font file with all the weights between 300 and 500. Alternatively, adding a ; between each weight will then only download weights 300 and 500 – so, for example, you can’t pick weight 301:

<link href="https://fonts.googleapis.com/css2?family=Yanone+Kaffeesatz:wght@300;500&display=swap" rel="stylesheet">

It took me a few minutes to figure out what was going wrong and why the font wasn’t loading at all, so hopefully the Google Fonts team can make that a bit clearer with the embed codes in the future. Perhaps there should be an option or a toggle somewhere to select a range or specific weights (or maybe I just didn’t see it).

Either way, I think all this is why variable fonts can be so gosh darn helpful; they allow us to adjust text in ways that we’ve never been able to do before. So, yay for variable fonts!

The post Dark mode and variable fonts appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Let’s Say You Were Going to Write a Blog Post About Dark Mode

This is not that blog post. I’m saying let’s say you were.

This is not a knock any other blog posts out there about Dark Mode. There are lots of good ones, and I’m a fan of any information-sharing blog post. This is more of a thought exercise on what I think it would take to write a really great blog post on this subject.

  • You’d explain what Dark Mode is. You wouldn’t dwell on it though, because chances are are people reading a blog post like this already essentially know what it is.
  • You’d definitely have a nice demo. Probably multiple demos. One that is very basic so the most important lines of code can be easily seen. Perhaps something that swaps out background-color and color. The other demo(s) will deal with more complex and real-world scenarios. What do you do with images and background images? SVG strokes and fills? Buttons? Borders? Shadows? These are rare things that sites have, so anyone looking at designing a Dark Mode UI will come across them.
  • You’d deal with the fact that Dark Mode is a choice that can happen at the operating system level itself. Fortunately, we can detect that in CSS, so you’ll have to cover how.
  • JavaScript might need to know about the operating system choice as well. Perhaps because some styling is happening at the JavaScript level, but also because of this next thing.
  • Dark Mode could (should?) be a choice on the website as well. That servers cases where, on this particular site, a user prefers a choice opposite of what their operating system preference is.
  • Building a theme toggle isn’t a small job. If your site has authentication, that choice should probably be remembered at the account level. If it doesn’t, the choice should be remembered in some other way. One possibility is localStorage, but that can have problems, like the fact that CSS is generally applied to a page before JavaScript executes, meaning you’re facing a “flash of incorrect theme” situation. You might be needing to deal with cookies so that you can send theme-specific CSS on each page load.
  • Your blog post would include real-world examples of people already doing this. That way, you can investigate how they’ve done it and evaluate how successful they were. Perhaps you can reach out to them for comment as well.
  • You’ll be aware of other writing on this subject. That should not dissuade you from writing about the subject yourself, but a blog post that sounds like you’re the first and only person writing about a subject when you clearly aren’t has an awkward tone to it that doesn’t come across well. Not only can you learn from others’ writing, but you can also pull from it and potentially take it further.
  • Since you’ll be covering browser technology, you’ll be covering the support of that technology across the browser landscape. Are there notable exceptions in support? Is that support coming? Have you researched what browsers themselves are saying about the technology?
  • There are accessibility implications abound. Dark Mode itself can be considered an accessibility feature, and there are tangential accessibility issues here too, like how the toggle works, how mode changes are announced, and a whole new set of color contrasts to calculate and get right. A blog post is a great opportunity to talk about all that. Have you researched it? Have you talked to any people who have special needs around these features? Any experts? Have you read what accessibility people are saying about Dark Mode?

That was all about Dark Mode, but I bet you could imagine how considering all these points could benefit any blog post covering a technical concept.

The post Let’s Say You Were Going to Write a Blog Post About Dark Mode appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , ,
[Top]

The Ultimate Guide to Dark Mode for Email Marketers

On the regular web (I suppose) we handle “dark mode” with the CSS prefers-color-scheme media query. But, and to nobody’s surprise, it’s way weirder in the land of HTML email. The weirdness is that across different email clients, they handle the dark mode thing differently, starting with the fact that the email client itself might have its own toggle for dark mode.

Say that toggle has dark mode activated. There are three things that might happen:

  1. The UI of the app goes dark mode, but it leaves the email alone (e.g. Apple Mail).
  2. It tries to apply dark mode to your emails as well, but only when it detects areas that are light. Those areas become dark while leaving dark areas alone (e.g. Outlook.com).
  3. It goes full-bore and force-inverts email colors (e.g. Gmail app on iOS 13).

That last one is wacky-town. As Alice Li says:

This is the most invasive color scheme: it not only inverts the areas with light backgrounds but impacts dark backgrounds as well. So if you already designed your emails to have a dark theme, this scheme will ironically force them to become light.
Emphasis hers.

Direct Link to ArticlePermalink

The post The Ultimate Guide to Dark Mode for Email Marketers appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Dark Mode Favicons

Oooo! A bonafide trick from Thomas Steiner. Chrome will soon be supporting SVG favicons (e.g. <link rel="icon" href="/icon.svg">). And you can embed CSS within an SVG with a <style> element. That CSS can use a perfers-color-sceme media query, and as a result, a favicon that supports dark mode!

<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">   <style>     circle {       fill: yellow;       stroke: black;       stroke-width: 3px;     }     @media (prefers-color-scheme: dark) {       circle {         fill: black;         stroke: yellow;       }     }   </style>   <circle cx="50" cy="50" r="47"/> </svg>

Safari also supports SVG, but it’s different…

<link rel="mask-icon" href="/favicon.svg" color="#990000">

You specify the color, so there is no opportunity there for a dark mode situation. A little surprising, since Apple is so all-in on this dark mode stuff. I have no idea if they plan to address that or what.

The post Dark Mode Favicons appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

“Headless Mode”

A couple of months ago, we invited Marc Anton Dahmen to show off his database-less content management system (CMS) Automad. His post is an interesting inside look at templating engines, including how they work, how CMSs use them, and how they impact the way we write things, such as loops.

Well, Automad just released version 1.3.0 and it introduces a “headless” mode that brings it more in line with where the CMS landscape seems to be headed (pun intended).

And what the heck is a “headless” CMS? I always find that name to be a little weird, but the idea is that the engine for content creation is fully separated from the front-end display and instead stitched together by APIs. This means we’re able to get all the wonderful benefits of creating content in a CMS without being locked into its templating requirements. Chris has a more thorough explanation of the concept from a few years back.

A good example is WordPress and its REST API. We still enjoy the easy UI and extensible administrative features of WordPress, but can send the data anywhere via API to create the front end. Rather write your templates in JavaScript instead of PHP? Go for it!

If the CMS is a body and the front-end view is the head, then the body can unscrew its head and replace it with another. Weird, right?

In any case, whether it’s Automad, WordPress, Sanity, Contentful, Ghost, Netlify CMS, or any others in the growing number of API-first options out there, the move toward headless is a space to watch. HeadlessCMS.org is a good place to do that. We could see vast changes that lead to both better content and developer experiences, which is what all of this is trying to accomplish.

The post “Headless Mode” appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

Weekly Platform News: Internet Explorer Mode, Speed Report in Search Console, Restricting Notification Prompts

In this week’s roundup: Internet Explorer finds its way into Edge, Google Search Console touts a new speed report, and Firefox gives Facebook’s notification the silent treatment.

Let’s get into the news!

Edge browser with new Internet Explorer mode launches in January

Microsoft expects to release the new Chromium-based Edge browser on January 15, on both Windows and macOS. This browser includes a new Internet Explorer mode that allows Edge to automatically and seamlessly render tabs containing specific legacy content (e.g., a company’s intranet) using Internet Explorer’s engine instead of Edge’s standard engine (Blink).

Here’s a sped-up excerpt from Fred Pullen’s presentation that shows the new Internet Explorer mode in action:

(via Kyle Pflug)

Speed report experimentally available in Google Search Console

The new Speed report in Google’s Search Console shows how your website performs for real-world Chrome users (both on mobile and desktop). Pages that “pass a certain threshold of visits” are categorized into fast, moderate, and slow pages.

Tip: After fixing a speed issue, use the “Validate fix” button to notify Google Search. Google will verify the fix and re-index the pages if the issue is resolved.

(via Google Webmasters)

Facebook’s notification prompt will disappear in Firefox

Firefox will soon start blocking notification prompts on websites that request the notification permission immediately on page load (Facebook does this). Instead of the prompt, a small “speech balloon” icon will be shown in the URL bar.

Websites will still be able to show a notification prompt in Firefox as long as they request permission in response to a user interaction (a click, tap, or key press).

(via Marcos Càceres)

More news…

Read more news in my weekly newsletter for web developers. Pledge as little as $ 2 per month to get the latest news from me via email every Monday.

More News →

The post Weekly Platform News: Internet Explorer Mode, Speed Report in Search Console, Restricting Notification Prompts appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , , , , , ,
[Top]

A Dark Mode Toggle with React and ThemeProvider

I like when websites have a dark mode option. Dark mode makes web pages easier for me to read and helps my eyes feel more relaxed. Many websites, including YouTube and Twitter, have implemented it already, and we’re starting to see it trickle onto many other sites as well.

In this tutorial, we’re going to build a toggle that allows users to switch between light and dark modes, using a <ThemeProvider wrapper from the styled-components library. We’ll create a useDarkMode custom hook, which supports the prefers-color-scheme media query to set the mode according to the user’s OS color scheme settings.

If that sounds hard, I promise it’s not! Let’s dig in and make it happen.

See the Pen
Day/night mode switch toggle with React and ThemeProvider
by Maks Akymenko (@maximakymenko)
on CodePen.

Let’s set things up

We’ll use create-react-app to initiate a new project:

npx create-react-app my-app cd my-app yarn start

Next, open a separate terminal window and install styled-components:

yarn add styled-components

Next thing to do is create two files. The first is global.js, which will contain our base styling, and the second is theme.js, which will include variables for our dark and light themes:

// theme.js export const lightTheme = {   body: '#E2E2E2',   text: '#363537',   toggleBorder: '#FFF',   gradient: 'linear-gradient(#39598A, #79D7ED)', }  export const darkTheme = {   body: '#363537',   text: '#FAFAFA',   toggleBorder: '#6B8096',   gradient: 'linear-gradient(#091236, #1E215D)', }

Feel free to customize variables any way you want, because this code is used just for demonstration purposes.

// global.js // Source: https://github.com/maximakymenko/react-day-night-toggle-app/blob/master/src/global.js#L23-L41  import { createGlobalStyle } from 'styled-components';  export const GlobalStyles = createGlobalStyle`   *,   *::after,   *::before {     box-sizing: border-box;   }    body {     align-items: center;     background: $ {({ theme }) => theme.body};     color: $ {({ theme }) => theme.text};     display: flex;     flex-direction: column;     justify-content: center;     height: 100vh;     margin: 0;     padding: 0;     font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;     transition: all 0.25s linear;   }

Go to the App.js file. We’re going to delete everything in there and add the layout for our app. Here’s what I did:

import React from 'react'; import { ThemeProvider } from 'styled-components'; import { lightTheme, darkTheme } from './theme'; import { GlobalStyles } from './global';  function App() {   return (     <ThemeProvider theme={lightTheme}>       <>         <GlobalStyles />         <button>Toggle theme</button>         <h1>It's a light theme!</h1>         <footer>         </footer>       </>     </ThemeProvider>   ); }  export default App;

This imports our light and dark themes. The ThemeProvider component also gets imported and is passed the light theme (lightTheme) styles inside. We also import GlobalStyles to tighten everything up in one place.

Here’s roughly what we have so far:

Now, the toggling functionality

There is no magic switching between themes yet, so let’s implement toggling functionality. We are only going to need a couple lines of code to make it work.

First, import the useState hook from react:

// App.js import React, { useState } from 'react';

Next, use the hook to create a local state which will keep track of the current theme and add a function to switch between themes on click:

// App.js const [theme, setTheme] = useState('light');  // The function that toggles between themes const toggleTheme = () => {   // if the theme is not light, then set it to dark   if (theme === 'light') {     setTheme('dark');   // otherwise, it should be light   } else {     setTheme('light');   } }

After that, all that’s left is to pass this function to our button element and conditionally change the theme. Take a look:

// App.js import React, { useState } from 'react'; import { ThemeProvider } from 'styled-components'; import { lightTheme, darkTheme } from './theme'; import { GlobalStyles } from './global';  // The function that toggles between themes function App() {   const [theme, setTheme] = useState('light');   const toggleTheme = () => {     if (theme === 'light') {       setTheme('dark');     } else {       setTheme('light');     }   }      // Return the layout based on the current theme   return (     <ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>       <>         <GlobalStyles />         // Pass the toggle functionality to the button         <button onClick={toggleTheme}>Toggle theme</button>         <h1>It's a light theme!</h1>         <footer>         </footer>       </>     </ThemeProvider>   ); }  export default App;

How does it work?

// global.js background: $ {({ theme }) => theme.body}; color: $ {({ theme }) => theme.text}; transition: all 0.25s linear;

Earlier in our GlobalStyles, we assigned background and color properties to values from the theme object, so now, every time we switch the toggle, values change depending on the darkTheme and lightTheme objects that we are passing to ThemeProvider. The transition property allows us to make this change a little more smoothly than working with keyframe animations.

Now we need the toggle component

We’re generally done here because you now know how to create toggling functionality. However, we can always do better, so let’s improve the app by creating a custom Toggle component and make our switch functionality reusable. That’s one of the key benefits to making this in React, right?

We’ll keep everything inside one file for simplicity’s sake,, so let’s create a new one called Toggle.js and add the following:

// Toggle.js import React from 'react' import { func, string } from 'prop-types'; import styled from 'styled-components'; // Import a couple of SVG files we'll use in the design: https://www.flaticon.com import { ReactComponent as MoonIcon } from 'icons/moon.svg'; import { ReactComponent as SunIcon } from 'icons/sun.svg';  const Toggle = ({ theme, toggleTheme }) => {   const isLight = theme === 'light';   return (     <button onClick={toggleTheme} >       <SunIcon />       <MoonIcon />     </button>   ); };  Toggle.propTypes = {   theme: string.isRequired,   toggleTheme: func.isRequired, }  export default Toggle;

You can download icons from here and here. Also, if we want to use icons as components, remember about importing them as React components.

We passed two props inside: the theme will provide the current theme (light or dark) and toggleTheme function will be used to switch between them. Below we created an isLight variable, which will return a boolean value depending on our current theme. We’ll pass it later to our styled component.

We’ve also imported a styled function from styled-components, so let’s use it. Feel free to add this on top your file after the imports or create a dedicated file for that (e.g. Toggle.styled.js) like I have below. Again, this is purely for presentation purposes, so you can style your component as you see fit.

// Toggle.styled.js const ToggleContainer = styled.button`   background: $ {({ theme }) => theme.gradient};   border: 2px solid $ {({ theme }) => theme.toggleBorder};   border-radius: 30px;   cursor: pointer;   display: flex;   font-size: 0.5rem;   justify-content: space-between;   margin: 0 auto;   overflow: hidden;   padding: 0.5rem;   position: relative;   width: 8rem;   height: 4rem;    svg {     height: auto;     width: 2.5rem;     transition: all 0.3s linear;          // sun icon     &:first-child {       transform: $ {({ lightTheme }) => lightTheme ? 'translateY(0)' : 'translateY(100px)'};     }          // moon icon     &:nth-child(2) {       transform: $ {({ lightTheme }) => lightTheme ? 'translateY(-100px)' : 'translateY(0)'};     }   } `;

Importing icons as components allows us to directly change the styles of the SVG icons. We’re checking if the lightTheme is an active one, and if so, we move the appropriate icon out of the visible area — sort of like the moon going away when it’s daytime and vice versa.

Don’t forget to replace the button with the ToggleContainer component in Toggle.js, regardless of whether you’re styling in separate file or directly in Toggle.js. Be sure to pass the isLight variable to it to specify the current theme. I called the prop lightTheme so it would clearly reflect its purpose.

The last thing to do is import our component inside App.js and pass required props to it. Also, to add a bit more interactivity, I’ve passed condition to toggle between “light” and “dark” in the heading when the theme changes:

// App.js <Toggle theme={theme} toggleTheme={toggleTheme} /> <h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!</h1>

Don’t forget to credit the flaticon.com authors for the providing the icons.

// App.js <span>Credits:</span> <small><b>Sun</b> icon made by <a href="https://www.flaticon.com/authors/smalllikeart">smalllikeart</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small> <small><b>Moon</b> icon made by <a href="https://www.freepik.com/home">Freepik</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small>

Now that’s better:

The useDarkMode hook

While building an application, we should keep in mind that the app must be scalable, meaning, reusable, so we can use in it many places, or even different projects.

That is why it would be great if we move our toggle functionality to a separate place — so, why not to create a dedicated account hook for that?

Let’s create a new file called useDarkMode.js in the project src directory and move our logic into this file with some tweaks:

// useDarkMode.js import { useEffect, useState } from 'react';  export const useDarkMode = () => {   const [theme, setTheme] = useState('light');   const toggleTheme = () => {     if (theme === 'light') {       window.localStorage.setItem('theme', 'dark')       setTheme('dark')     } else {       window.localStorage.setItem('theme', 'light')       setTheme('light')     }   };    useEffect(() => {     const localTheme = window.localStorage.getItem('theme');     localTheme && setTheme(localTheme);   }, []);    return [theme, toggleTheme] };

We’ve added a couple of things here. We want our theme to persist between sessions in the browser, so if someone has chosen a dark theme, that’s what they’ll get on the next visit to the app. That’s a huge UX improvement. For this reasons we use localStorage.

We’ve also implemented the useEffect hook to check on component mounting. If the user has previously selected a theme, we will pass it to our setTheme function. In the end, we will return our theme, which contains the chosen theme and toggleTheme function to switch between modes.

Now, let’s implement the useDarkMode hook. Go into App.js, import the newly created hook, destructure our theme and toggleTheme properties from the hook, and, put them where they belong:

// App.js import React from 'react'; import { ThemeProvider } from 'styled-components'; import { useDarkMode } from './useDarkMode'; import { lightTheme, darkTheme } from './theme'; import { GlobalStyles } from './global'; import Toggle from './components/Toggle';  function App() {   const [theme, toggleTheme] = useDarkMode();   const themeMode = theme === 'light' ? lightTheme : darkTheme;    return (     <ThemeProvider theme={themeMode}>       <>         <GlobalStyles />         <Toggle theme={theme} toggleTheme={toggleTheme} />         <h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!</h1>         <footer>           Credits:           <small>Sun icon made by smalllikeart from www.flaticon.com</small>           <small>Moon icon made by Freepik from www.flaticon.com</small>         </footer>       </>     </ThemeProvider>   ); }  export default App;

This almost works almost perfectly, but there is one small thing we can do to make our experience better. Switch to dark theme and reload the page. Do you see that the sun icon loads before the moon icon for a brief moment?

That happens because our useState hook initiates the light theme initially. After that, useEffect runs, checks localStorage and only then sets the theme to dark.

So far, I found two solutions. The first is to check if there is a value in localStorage in our useState:

// useDarkMode.js const [theme, setTheme] = useState(window.localStorage.getItem('theme') || 'light');

However, I am not sure if it’s a good practice to do checks like that inside useState, so let me show you a second solution, that I’m using.

This one will be a bit more complicated. We will create another state and call it componentMounted. Then, inside the useEffect hook, where we check our localTheme, we’ll add an else statement, and if there is no theme in localStorage, we’ll add it. After that, we’ll set setComponentMounted to true. In the end, we add componentMounted to our return statement.

// useDarkMode.js import { useEffect, useState } from 'react';  export const useDarkMode = () => {   const [theme, setTheme] = useState('light');   const [componentMounted, setComponentMounted] = useState(false);   const toggleTheme = () => {     if (theme === 'light') {       window.localStorage.setItem('theme', 'dark');       setTheme('dark');     } else {       window.localStorage.setItem('theme', 'light');       setTheme('light');     }   };    useEffect(() => {     const localTheme = window.localStorage.getItem('theme');     if (localTheme) {       setTheme(localTheme);     } else {       setTheme('light')       window.localStorage.setItem('theme', 'light')     }     setComponentMounted(true);   }, []);      return [theme, toggleTheme, componentMounted] };

You might have noticed that we’ve got some pieces of code that are repeated. We always try to follow the DRY principle while writing the code, and right here we’ve got a chance to use it. We can create a separate function that will set our state and pass theme to the localStorage. I believe, that the best name for it will be setTheme, but we’ve already used it, so let’s call it setMode:

// useDarkMode.js const setMode = mode => {   window.localStorage.setItem('theme', mode)   setTheme(mode) };

With this function in place, we can refactor our useDarkMode.js a little:

// useDarkMode.js import { useEffect, useState } from 'react'; export const useDarkMode = () => {   const [theme, setTheme] = useState('light');   const [componentMounted, setComponentMounted] = useState(false);    const setMode = mode => {     window.localStorage.setItem('theme', mode)     setTheme(mode)   };    const toggleTheme = () => {     if (theme === 'light') {       setMode('dark');     } else {       setMode('light');     }   };    useEffect(() => {     const localTheme = window.localStorage.getItem('theme');     if (localTheme) {       setTheme(localTheme);     } else {       setMode('light');     }     setComponentMounted(true);   }, []);    return [theme, toggleTheme, componentMounted] };

We’ve only changed code a little, but it looks so much better and is easier to read and understand!

Did the component mount?

Getting back to componentMounted property. We will use it to check if our component has mounted because this is what happens in useEffect hook.

If it hasn’t happened yet, we will render an empty div:

// App.js if (!componentMounted) {   return <div /> };

Here is how complete code for the App.js:

// App.js import React from 'react'; import { ThemeProvider } from 'styled-components'; import { useDarkMode } from './useDarkMode'; import { lightTheme, darkTheme } from './theme'; import { GlobalStyles } from './global'; import Toggle from './components/Toggle';  function App() {   const [theme, toggleTheme, componentMounted] = useDarkMode();    const themeMode = theme === 'light' ? lightTheme : darkTheme;    if (!componentMounted) {     return <div />   };    return (     <ThemeProvider theme={themeMode}>       <>         <GlobalStyles />         <Toggle theme={theme} toggleTheme={toggleTheme} />         <h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!</h1>         <footer>           <span>Credits:</span>           <small><b>Sun</b> icon made by <a href="https://www.flaticon.com/authors/smalllikeart">smalllikeart</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small>           <small><b>Moon</b> icon made by <a href="https://www.freepik.com/home">Freepik</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small>         </footer>       </>     </ThemeProvider>   ); }  export default App;

Using the user’s preferred color scheme

This part is not required, but it will let you achieve even better user experience. This media feature is used to detect if the user has requested the page to use a light or dark color theme based on the settings in their OS. For example, if a user’s default color scheme on a phone or laptop is set to dark, your website will change its color scheme accordingly to it. It’s worth noting that this media query is still a work in progress and is included in the Media Queries Level 5 specification, which is in Editor’s Draft.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Opera Firefox IE Edge Safari
76 62 67 No 76 12.1

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
13 No No 76 No 68

The implementation is pretty straightforward. Because we’re working with a media query, we need to check if the browser supports it in the useEffect hook and set appropriate theme. To do that, we’ll use window.matchMedia to check if it exists and whether dark mode is supported. We also need to remember about the localTheme because, if it’s available, we don’t want to overwrite it with the dark value unless, of course, the value is set to light.

If all checks are passed, we will set the dark theme.

// useDarkMode.js useEffect(() => { if (   window.matchMedia &&   window.matchMedia('(prefers-color-scheme: dark)').matches &&    !localTheme ) {   setTheme('dark')   } })

As mentioned before, we need to remember about the existence of localTheme — that’s why we need to implement our previous logic where we’ve checked for it.

Here’s what we had from before:

// useDarkMode.js useEffect(() => { const localTheme = window.localStorage.getItem('theme');   if (localTheme) {     setTheme(localTheme);   } else {     setMode('light');   } })

Let’s mix it up. I’ve replaced the if and else statements with ternary operators to make things a little more readable as well:

// useDarkMode.js useEffect(() => { const localTheme = window.localStorage.getItem('theme'); window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && !localTheme ?   setMode('dark') :   localTheme ?     setTheme(localTheme) :     setMode('light');}) })

Here’s the userDarkMode.js file with the complete code:

// useDarkMode.js import { useEffect, useState } from 'react';  export const useDarkMode = () => {   const [theme, setTheme] = useState('light');   const [componentMounted, setComponentMounted] = useState(false);   const setMode = mode => {     window.localStorage.setItem('theme', mode)     setTheme(mode)   };    const toggleTheme = () => {     if (theme === 'light') {       setMode('dark')     } else {       setMode('light')     }   };    useEffect(() => {     const localTheme = window.localStorage.getItem('theme');     window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && !localTheme ?       setMode('dark') :       localTheme ?         setTheme(localTheme) :         setMode('light');     setComponentMounted(true);   }, []);    return [theme, toggleTheme, componentMounted] };

Give it a try! It changes the mode, persists the theme in localStorage, and also sets the default theme accordingly to the OS color scheme if it’s available.


Congratulations, my friend! Great job! If you have any questions about implementation, feel free to send me a message!

The post A Dark Mode Toggle with React and ThemeProvider appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Night Mode with Mix Blend Mode: Difference

Dark mode designs are all the rage right now but here’s an interesting take: Wei Gao has built a night mode on her own site that uses mix-blend-mode: difference to create an effect that looks like this:

Wei explains how she implemented this technique and the edge cases she encountered along the way. I especially love what she had to say about mix-blend-mode functions here:

I remember first playing around with them in Photoshop years ago. Now that browsers are becoming more powerful and we are seeing complex graphical features native to browser rendering. This doesn’t mean we should implement a full photoshop in browsers and nor should we limit our imaginations to just that. Browsers and web pages have their own contexts and goals, as well as a different set of limits. Maybe we should welcome them like new habitants and discover use cases native to this territory.

Indeed! Although Wei’s technique is pretty unique and awesome on its own, this all ties back into the thing that kicked off the whole trend: the prefers-color-scheme media feature that was released in Safari as part of the MacOS 10.4 release that gave us a dark mode preference setting. This is a developing space, so we’re certain to see more innovations and approaches ahead.

Direct Link to ArticlePermalink

The post Night Mode with Mix Blend Mode: Difference appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]