cssnode.jsvitelit

How to change CSS dynamically for whole site with vite and lit?


Dear all I am developing a node app with vite and lit. I like to have the option that the user can change the style dynamically at runtime for instance to select between a dark and bright theme for the whole site.

I know that you can directly import "css" files and apply it on single lit elements. But how I can change the style dynamically for the whole site? I also noticed that an ìmport from "xxxx.css" can not contain a dynamic variable,so the following seems not to be supported?

let theme = "dark"
import "folder/" + theme + ".css";

Any suggestion how I can best achieve my goal?


Solution

  • This is best handled by setting all your colors in your elements as CSS Custom Properties. e.g.

    class MyCustomCard extends LitElement {
      static styles = css`
        .card {
          /* Set custom property and default value */
          background-color: var(--sys-color-surface, #FFF);
          color: var(--sys-color-on-surface, #000);
          ...
        }
      `;
      ...
    }
    

    Then you can have a theming.css file at the root of your document. e.g.

    <!-- index.html -->
    <head>
      <link rel="stylesheet" href="theme.css">
    </head>
    
    /* theme.css */
    /* light theme */
    :root, body.force-light-mode {
      --sys-color-surface: #FFF;
      --sys-color-on-surface: #000;
      ...
    }
    /* auto dark theme */
    @media (prefers-color-scheme: dark) {
      :root {
        --sys-color-surface: #000;
        --sys-color-on-surface: #FFF;
        ...
      }
    }
    
    /* user preference dark mode */
    body.force-dark-mode {
      /* This can be deduplicated with an @import https://developer.mozilla.org/en-US/docs/Web/CSS/@import */
      --sys-color-surface: #000;
      --sys-color-on-surface: #FFF;
      ...
    }
    

    Then you can have a dark theme toggle that toggles the force-*-mode class on body:

    class ThemeToggle extends LitElement {
      render() {
        return html`
          <div>Select Theme</div>
          <label>
            Light Mode
            <input
                type="radio"
                @change=${this.onChange('light')}
                name="theme">
           </label>
          <label>
            Follow System
            <input
                type="radio"
                @change=${this.onChange('system')}
                name="theme"
                checked>
           </label>
          <label>
            Dark Mode
            <input
                type="radio"
                @change=${this.onChange('dark')}
                name="theme">
           </label>`
      }
    
      onChange = (mode) => (e) => {
        if (e.target.checked) {
          switch(mode) {
            case 'light':
              document.body.classList.add('force-light-mode');
              break;
            case 'dark':
              document.body.classList.add('force-dark-mode');
              break;
          }
        } else {
          switch(mode) {
            case 'light':
              document.body.classList.remove('force-light-mode');
              break;
            case 'dark':
              document.body.classList.remove('force-dark-mode');
              break;
          }
        }
      }
    }