javascripthtmlcss

Dark mode flickers a white background for a millisecond on reload


I am trying to add this dark mode feature in my app. It uses localstorage to store the user's preference for future usage. So the problem now is when the dark mode is enabled, and the page is reloaded for some reason, eg. if the user deliberately reloads the page, or submits a form, then there's a flicker of white background all over the page before it turns to be dark. It stays a fraction of a second. It just doesn't look professional.

Haven't found any solution yet. So please help me out.

PS. The snippet below won't work here in SO as the code includes localStorage object.

Here's the code:

const toggleSwitch = document.querySelector('#dark-mode-button input[type="checkbox"]');
const currentTheme = localStorage.getItem('theme');

if (currentTheme) {
    document.documentElement.setAttribute('data-theme', currentTheme);
    if (currentTheme === 'dark') {
            toggleSwitch.checked = true;
    }
}

function switchTheme(e) {
    if (e.target.checked) {
        document.documentElement.setAttribute('data-theme', 'dark');
        localStorage.setItem('theme', 'dark');
    }else {        
        document.documentElement.setAttribute('data-theme', 'light');
        localStorage.setItem('theme', 'light');
    }    
}

toggleSwitch.addEventListener('change', switchTheme, false);  
:root {
  --primary-color: #495057;
  --bg-color-primary: #F5F5F5;
}

body{
  background-color: var(--bg-color-primary); 
}

[data-theme="dark"] {
  --primary-color: #8899A6;
  --bg-color-primary: #15202B;
}

table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
  background-color: #fff;
}

td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}
<div id="dark-mode-button">
    <input id="chck" type="checkbox">Dark Mode
    <label for="chck" class="check-trail">
      <span class="check-handler"></span>
    </label>
</div>

<table class="table">
    <thead>
      <tr>
          <th>Header 1</th>
          <th>Header 2</th>
          <th>Header 3</th>
      </tr>
    </thead>  
    <tbody>
      <tr>
        <td>Alfreds Futterkiste</td>
        <td>Maria Anders</td>
        <td>Germany</td>
      </tr>
    </tbody>                     
</table>


Solution

  • It would be ideal to block the page rendering by placing a small <script> tag inside the <head> of your Document. By doing so the DOM parser should stop and call the JavaScript interpreter, assign the data-theme attribute to <html> and then continue where left.

    Place this <script> inside <head> - even before the <link> or <style> tags:

    <head>
      <script>
        // IMPORTANT: set this in <HEAD> top before any other tag.
        const setTheme = (theme) => {
          theme ??= localStorage.theme || "light";
          document.documentElement.dataset.theme = theme;
          localStorage.theme = theme;
        };
        setTheme();
      </script>
    
      <!-- meta, title, etc... -->
      <!-- link, style, etc... -->
    </head>
    

    Then, right before the closing </body> tag use all the other scripts in a non-render-blocking manner:

    <script src="js/index.js"></script>
    <!-- other <script> tags here -->
    <!-- Closing </body> </html> goes here -->
    

    and inside your i.e: js/index.js file use:

    const elToggleTheme = document.querySelector('#dark-mode-button input[type="checkbox"]');
    
    elToggleTheme.checked = localStorage.theme === "dark";
    
    elToggleTheme.addEventListener("change", () => {
      const theme = elToggleTheme.checked ? "dark" : "light";
      setTheme(theme);
    });
    

    PS: You can also place the above JavaScript code into your .js file if you want to keep all your scripts in one place.

    If you need a creative idea for a Dark theme mode toggle checkbox see this answer: Toggle theme button