Theme Provider

Wrap your app, probably at the router, with ThemeProvider:

...
<Stylesheet id="leptos" href="/pkg/my_app.css" />
<ThemeProvider>
    <Router>
        ...
    </Router>
</ThemeProvider>
...

Load your own stylesheets before ThemeProvider to avoid conflicts.

Setting the Default Theme and Mode

By default, the theme provider expands to look like this:

<ThemeProvider mode="auto" theme=Theme::Default>

ThemeProvider currently has three modes:

These modes correspond to parts of the theme, in this case Theme::Default. Each theme has three parts which are just a collection of CSS variables that override the main template:

  1. common
  2. dark
  3. light

Common contains variables common to both light and dark mode, dark contains variables only used for dark mode, and light contains variables only used for light mode. You can create your own arbitrary Theme using this format and introduce it here. This is also where you can include any arbitrary CSS that you want to load reactively.

Updating the Theme and Mode

ThemeProvider also provides ThemeProviderContext to its children. Here is an example showing how to update the current Mode:

#[component]
pub fn ThemeModeSwitcher() -> impl IntoView {
    let theme_context = expect_context::<ThemeProviderContext>();
    let theme_mode = RwSignal::new("auto".to_string());

    Effect::new(move || theme_context.set(theme_mode.get().into()));

    view!{
        <RadioGroup value=theme_mode>
            <legend>"Theme Mode"</legend>
            <Radio value="auto">"Auto"</Radio>
            <Radio value="dark">"Dark"</Radio>
            <Radio value="light">"Light"</Radio>
        </RadioGroup>
    }
}

For a more in-depth example of how to use the theme system, including cookies, check out the source code for this docs website.

Dark Themes and Non-SSR Render Modes

Note that if you're doing client-side rendering with trunk, and using a dark theme, some browsers may briefly flash a default background (typically white) on page reload breaking any dark themes. This is something we can't do anything about at library level, but we can do something at app level. A simple workaround is to force a background color with CSS in index.html:

<!-- index.html -->
...
<head>
...
<style>
  @media (prefers-color-scheme: dark) {
    :root {
      background-color: #0a0a0a;
    }
  }
</style>
...
</head>
...

This isn't magic however, you'll now have the opposite problem, and possibly more problems if you have a light/dark toggle feature in your app. When doing CSR you'll just have to choose to either deal with the flashing or have only a light or dark theme.

Islands has this same issue but it cannot be solved. The background will flash white on every page navigation or reload because it triggers a full repaint.