Dark Mode – Magento 2 Theming

Dark Mode

Dark mode is an alternate color theme that makes the interface of your device dark. The latest dark mode trend has thrilled the customers all over the globe and they are requesting the dark mode support for their website theme almost exclusively.

The theme became popular because of the new technology on mobile devices – amoled screens. This technology has the ability to completely turn off the dark pixels and thus save the battery. And of course, it is easy on the eyes, which makes it even more effective.

System-wide dark mode in Android 10 is just one example of dark theme usage, but there are many other apps using it, such as YouTube, Viber, Skype, Netflix, Hulu, iTunes, etc.

Dark Mode – The IDEA

In the past few years, our company has developed and used many cool features that were years ahead of our competition. The dark mode was one of those features which I personally suggested, noticing the trends. So, the task was to build a dark mode theme support for our dear client Rainier Arms, a technology leader in the firearms industry.

As a technology lover, I have really enjoyed working on this project and I have given many suggestions on what to use in the future. The dark mode turned out to be a great choice for Rainier Arms for more than one reason. Apart from the visual improvement, one of the main advantages is certainly the marketing aspect of this idea – clients can advertise it to attract more customers.

Update

I presented this feature to another client as well and they loved it. They immediately started coming up with the ideas applying the functionality of a dark mode theme.

Being in the fashion industry, the client saw this opportunity as a marketing stunt. Their first idea was – to paint the website dark on Black Friday.

The first task was not to allow the customers to paint-to-dark the website, but instead, let the admins force it on the users when they want to. Like on Black Friday, for example, or Halloween. Moreover, it can just be used on some category pages with premium products.

The brainstorming went on. An update will be posted once we figure out the requests and the implementation.

Drawing Board

There are several ways to build this functionality. Let’s start with the request that this feature should be available to all users, even the first-time comers. In addition to it, when the dark mode is selected on the website it should remain unchanged.

  • Account-based – this means that your customers must be logged in as for the feature to be active. But when a user switches devices, the feature will remain active.
  • Browser-based – you can use a cookie and save it for, let’s say, a year, and the feature will be active for a while. The downside of it is switching devices or clearing the cookies which will reset the option.
  • Both – another possibility is to use a combo, i.e., the best of both worlds. Here you can use the cookie but also have this attribute saved on the customer’s DB table (maybe use customer attributes for Magento Commerce) so you could read from it when switching devices/on cookies removal. In my opinion, the account-based option would have priority over the cookie. The only problem with this one is that you are saving one piece of information two times.

Our choice was to go with the browser-based solution because there was no need to additionally complicate the process by saving the data on the customer’s DB table.

Magento 2 Plugin

Magento 2 plugin is a powerful thing when you use it the right way. While searching for the solutions online I have found the piece of code that adds a CSS class dynamically to the body of your HTML layout. It is written by Sam Granger and the best way to use it really is the way he did it. Dynamically add a (static) CSS class to the body, based on your store or your store view. This way you can do a store-specific styling only by using CSS.

Using this method, I have written a piece of code that will read from the cookie and set the dark mode class in the body if a user turns on the dark mode. This method worked until I turned on the full page cache (FPC). This way, the first visit caches in the dark-mode class (or not) and displays in that form to all the other users. A different approach was inevitable.

What if we use a phtml block with cacheable=”false”, but then I remembered that Magento Dev Docs said: “Magento disables page caching if at least one non-cacheable block is present in the layout.”

My Way (improvisation that works)

What you need are the following things:

  • Two logos, of course, one for each background. So, a dark logo for the bright background and a bright logo for the dark background – to be clear.
  • Button, checkbox or CSS styled switch, similar to the one I used (props for Andreas Storm).

Here is the code if you want to use it:

  • A bit of Magento 2 xml knowledge
  • Some JS / Jquery, Magento phtml and CSS knowledge
  • And maybe a Dark Mode design mockup. I did it approximately – by the feeling.

The Code

The idea is to set a dark-mode class to the body of your HTML structure so that all the other elements inherit the styling based on that class. You need the class set as soon as possible to avoid the flickering of the website and the part where everything loads with white background and then flips to dark.

Technically, you need to read from the cookie and set the class to the body the moment the body is rendered. The best way to do this is by using a custom JS which will load right after the body opens. Let’s use it from the phtml block which will be the first one rendered.

So you should add a phtml block inside (themes) default.xml of the body tag under the head.components.

XML component

<referenceBlock name="head.components">
   <block as="dark.class" class="Magento\Framework\View\Element\Template" name="dark.class" template="Magento_Theme::html/header/set-dark-mode-class.phtml"/>
</referenceBlock>

The phtml block consists of some pure javascript and the HTML code for the button itself.

The Button

Place the button in the same phtml block just for the ease of use. At first, it is going to be hidden. After the DOM loads, we can move it to the footer and fade it in.

<div class="dark-mode-flip" id="dark-mode-flip">
   <div class="dark-mode-box">
       <input type="checkbox" id="dark-mode-cbx" style="display:none"/>
       <label for="dark-mode-cbx" class="toggle">
           <span>
             <svg width="10px" height="10px" viewBox="0 0 10 10">
               <path d="M5,1 L5,1 C2.790861,1 1,2.790861 1,5 L1,5 C1,7.209139 2.790861,9 5,9 L5,9 C7.209139,9 9,7.209139 9,5 L9,5 C9,2.790861 7.209139,1 5,1 L5,9 L5,1 Z"></path>
             </svg>
           </span>
       </label>
   </div>
   <p>Dark mode is <span id="#dark-mode-state"></span></p>
</div>

Under the paragraph, tag render the label about the current state to make it more explanatory.

Getting The Cookie Value

<script>
function getCookie(cname) {
   var name = cname + "=";
   var decodedCookie = decodeURIComponent(document.cookie);
   var ca = decodedCookie.split(';');
   for(var i = 0; i <ca.length; i++) {
       var c = ca[i];
       while (c.charAt(0) == ' ') {
           c = c.substring(1);
       }
       if (c.indexOf(name) == 0) {
           return c.substring(name.length, c.length);
       }
   }
   return "";
}
</script>

And here is the function that will check if the cookie is “on” and set the body class. Just to be safe, you may add an else branch in order to remove the class if the cookie is not set or the value is changed since the dark mode is not the website’s default state.

The Rest of the Code That Sets the Body Class

<script>
   var darkModeCookie = getCookie('dark-mode');
   var body = document.body;
   if (darkModeCookie === 'on') {
       // Set class
       body.classList.add('dark-mode');
       // Add checked checkbox and set it to on - on the label
       document.getElementById('dark-mode-cbx').checked = true;
       document.getElementById('dark-mode-state').innerHTML='on';
   } else {
       // Remove class
       body.classList.remove('dark-mode');
       document.getElementById('dark-mode-state').innerHTML='off';
   }
</script>

Having ensured that the class is there on the body tag, you can continue. I had to make sure this time around that I succeed in tricking the cache. And viola. It worked!

Everything else is easy from this point on.

Next to the standard, dark logo, I added another logo with the CSS class white-logo and added a class to the dark logo as well, the dark-logo. I used these classes to hide/display these logos according to the mode, or technically to the body class. Both versions are in .svg format so no worries there about the sizing.

The Most Important Piece of Code

Here is the piece of code that will actually do something once you hit the switch.

We already had a JS file inside of the theme for this kind of stuff so I used that one. It required jquery and for DOM to be loaded. First, I moved the flip button to the right place and displayed it.

Make sure you created that element. Mine has this ID: id=”dark-mode-flip-wrapper” After the flip changes the position you need to have the dark mode cookie set and its value to on or off.

Important things to note are the expiry date of the cookie and the path it’s being saved to. In this case, I set the cookie to be valid for a year. Cookie path is another subject. Remember that a cookie saves paths for each URL individually if it is not specified.

In this case, I used the same path, the root path, to write to and read from. This way I ensured the usage of only one cookie for that mode on this website. The last piece of code here just updates the label “on” or “off”.

Please note: In case of the multi-website setup you need to pay closer attention to the cookie path. It may need to be different for each website if you need to separate the behavior of the dark mode on each website.

require([
       'jquery',
       'domReady!'
   ], function ($) {
       // Move dark mode button
       $('#dark-mode-flip').detach().appendTo('#dark-mode-flip-wrapper');
       $('.dark-mode-flip').fadeIn();
       // Toggle dark mode
       var darkModeCbx = '#dark-mode-cbx';
       $(darkModeCbx).click(function() {
           $(document.body).toggleClass('dark-mode');
           var isChecked = $(darkModeCbx).is(':checked'); // Returns true / false
           if(isChecked) {
               // Set cookie to on, expires in a year, path is / so it gets applied to all pages of the site
               document.cookie = 'dark-mode' + '=' + 'on' + '; ' + 'expires=' + new Date(new Date().getTime()+1000*60*60*24*365).toGMTString() + '; ' + 'path=/';
               document.getElementById('dark-mode-state').innerHTML='on';
           } else {
               // Set cookie to off, expires in a year, path is / so it gets applied to all pages of the site
               document.cookie = 'dark-mode' + '=' + 'off' + '; ' + 'expires=' + new Date(new Date().getTime()+1000*60*60*24*365).toGMTString() + '; ' + 'path=/';
               document.getElementById('dark-mode-state').innerHTML='off';
           }
       });
       // End of toggle dark mode
   }
);

The CSS

In my .less file, I set up a couple of variables for the colors, opened up the main class and put my styling under there.

/************************ == DARK MODE == **************************/
@darkest-bg: #111;
@second-dark: #1b1b1b;
@gray: #838383;
@dark-gray: #373737;
@white: #fff;
@bright: #eee;
@rainier-red: #db001f;

.dark-mode {
 background-color: @darkest-bg;
 color: @bright;

/* Your styling goes here. */

}
/********************** == end of DARK MODE == *********************/

Do a deploy and flush the cache. And that’s it.

You can test the theme here by flipping the switch from the footer.

Wrap Up

So, what are you waiting for? Start implementing these steps and follow the trend of practical and elegant dark mode theming. Contact us at [email protected] if you need some help with it.

5 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments