Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficient dark mode theming with toggle for mobile HTML

I have been looking around at ways to have a mobile efficient dark mode theme, that is both automatic and can be toggled. By mobile efficient I mean high-latency, low-bandwidth, low-CPU friendly:

  • only the relevant stylesheet is loaded (when light is active, dark stylesheet is not loaded, and vice-versa) to save on bandwidth
  • no full-screen flashing
  • no cookies

Solutions I have found do not have these properties, they are either using a fat CSS with both themes combined, or they're using two stylesheets, which can results in full-screen flashes when the user chose a theme different from the system default (as they are controlled through JS executed after the stylesheets are referenced in the DOM).

My best alternative so far is "efficient", but relies on document.write in a small inline script in the document head, it goes like:

let theme = localStorage.getItem("theme");
if (!theme) {
   theme = (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches)?"dark":"light"
}
if (theme == "dark") {
   document.write('<link rel="stylesheet" href="dark.theme.css" />');
} else {
   document.write('<link rel="stylesheet" href="light.theme.css" />');
}

The localStorage entry is managed by regular JS when manually toggled by the user.

I have tried all ways to mutate the link DOM element, and they all fail, resulting either in flashing or delayed CSS loading (with non-presented HTML being transiently visible).

I wonder if there is an approach that would still be efficient, support toggle and not rely on document.write ?

like image 841
Eric Grange Avatar asked Jun 12 '26 07:06

Eric Grange


1 Answers

How about adding a class "is-dark" to the html tag? To keep your css as lean as possible, you could define all your colors as css custom properties and redefine them whithin the scope of "is-dark". So you only have to deliver these custom properties twice, which should be manageable.

You still need JS to add the class, but you would not need document.write. That would eliminate the flicker in two of three cases, namely when the dark theme is selected by media query and when selecting it with the switch.

So this would look something like this:

:root {
  --clr-primary:                      #8e211a;
  --clr-secondary:                    #5e737a;
…
}


.is-dark {
  --clr-primary:                      black;
  --clr-secondary:                    white;
}


.myElement {
  color: var(--clr-secondary);
  background-color: var(--clr-primary);
}

That leaves us with the problem of the third case: Flickering after reload when the dark theme was selected per switch before the reload. In this case the light theme will be used until the JS hits and switches it to the dark theme.

The best bet seems to be blocking the page rendering until JS is ready as shown in this thread: Dark mode flickers a white background for a millisecond on reload

like image 55
Floyd Avatar answered Jun 13 '26 19:06

Floyd



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!