Light and Dark Theme in Browser with CSS in Rust

This article walks-through implementing dark and light theme using css.

Read time: 5 min
Written on March 28, 2023

I am a fan of dark theme, as brightness just hurts my eye. Hence I try to implement dark theme for apps that I build. (Not that I have build any popular apps.)

Most apps that I build these days are in rust. They usually end up being command line tools. As I find Building Graphical User Interface is hard in rust. Finally after trying out tauri and yew framework, I feel there is hope.

Basically TAURI is alternative to electron written in rust. And YEW is alternative rust framework to react.

So I built an app called time tracker which tracks time. And I wanted dark theme but without any frameworks. I got stuck and could not see how to proceed so I had paused work on the app. But finally, I found out the tricks for building dark theme.

The Dark Arts of Dark themes

CSS is a large standard for styling, it has lot of stuff. Usually I learn new trick by browsing stackoverflow or github browsing. I found about prefers-color-scheme which would allow theming to system preference.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
:root {
  --c-bg: #fff;
  --c-text: #000;
}
@media (prefers-color-scheme: dark) {
  :root {
    --c-bg: #000;
    --c-text: #fff;
  }
}

Based on users system preference the CSS color variables would be set. This could be used in the CSS styling for setting dark or light theme. This was fine, but the issue was the user could not toggle theme for particular app.

Dark or Light is just data

CSS selector provide way to select elements by data attributes. These attributes can be used to store information which are not actually part of HTML. We can store the theming information in here and toggle it to get desired result.

1
2
3
4
5
6
7
8
[data-theme="light"] {
  --bg: #1d2021;
  --fg: #d4be98;
}
[data-theme="light"] {
  --bg: #f9f5d7;
  --fg: #654735;
}

The above provides color variables to element with data attribute data-theme. We can set data-theme to document element via JavaScript and toggle it. This change would make the color variables available to all the elements.

1
document.documentElement.setAttribute("data-theme", "dark");

You can set default to system preference and later toggle when user asks. To get the system preference media queries can be used.

1
is_system_preference_dark = window.matchMedia("(prefers-color-scheme: light)").matches;

This is actually how this blog handles the theming. But this approach requires setting background-color and color properly to elements. When building app avoiding CSS framework it would be nice to utilize system colors, and change only app specific colors (like color for dangerous delete button) based on data attributes.

Only required colors, please!

For technical specification for CSS I refer to MDN. And I stumbled across color-scheme which allows elements to declare there preference. There is color scheme meta tag which can also be set to apply it for whole document.

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

This attribute can be toggle using JavaScript to change the theme mode. The elements use their default colors for background and font based on color scheme.

1
document.getElementsByTagName("meta").namedItem("color-scheme").content = "dark";

In some edge case you might want to reuse the default colors say for background. Hard coding does not cut it, the background color might be effected by system settings. Instead utilize system colors, this avoids hard coding of colors.

1
2
3
4
5
6
7
.remove-transparent {
    /*
        adding class to element with "background-color: transparent;"
        removes it's transparency.
    */
    background-color: Canvas;
}

What is the rusty way ?

This is all great but how to do it in rust? The theming requires interacting with browser, which is done through wasmbindgen. The easiest way would be to write JavaScript code and expose it to rust. This can be done using inline technique provided by the library.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#[wasm_bindgen(inline_js = r#"
  export function setDarkTheme() {
      document.getElementsByTagName('meta').namedItem('color-scheme').content = 'dark';
      document.documentElement.setAttribute('data-theme', 'light');
  }
"#)]
extern "C" {
    pub fn setDarkTheme();
}

//  in toggle function ..
setDarkTheme();
}

Resources

Post Tags:  #css,  #theme,  #html,  #dark,  #rust