Cory Rylan

My name is , Google Developer Expert, Speaker, Software Developer. Building Design Systems and Web Components.

Follow @coryrylan
CSS

Design System Architecture - Managing CSS Themes

Cory Rylan

- 3 minutes

Design Systems must help scale UI development across organizations and accross user devices. This includes theming capabilities such as light, dark and high contrast themes. This blog post will dive into managing CSS themes by showing strategies for dynamically loading themes and detecting which themes are available and even know when an individual theme has been loaded via events.

Detecting Active Themes

Using CSS variables to detect which theme is currently active is a powerful technique. By setting a unique CSS variable for each theme, you can easily check which theme is currently active.

To set up your CSS files for theme detection, define the --theme variable in each theme’s CSS file:

/* theme.dark.css */
:root {
  --theme: dark;
}
/* theme.light.css */
:root {
  --theme: light;
}

Then in JavaScript you can check for your active theme like so:

function getActiveTheme() {
  return getComputedStyle(document.documentElement).getPropertyValue('--theme');
}

Detecting Loaded Themes

Detecting which theme has been loaded is a similar but slightly different problem that which theme is currently being applied to our web page. We may want to determine what themes are available at runtime but not which theme is currently active. This can be useful for dynamically loading themes or for debugging purposes.

With this setup, each theme declares its unique identifier through a CSS variable, making it easy to detect which theme is active at any given time.

/* theme.dark.css */
:root {
  --theme: dark;
  --theme-dark: true;
}
/* theme.light.css */
:root {
  --theme: light;
  --theme-light: true;
}

Here’s a simple JavaScript function to detect if the theme is loaded:

function isThemeLoaded(value) {
    return getComputedStyle(document.documentElement).getPropertyValue('--theme') === value;
}

console.log(isThemeLoaded('dark'));

This function uses getComputedStyle to retrieve the value of the --theme-dark variable from the root element (:root). If the theme has been applied correctly, this value will return true, indicating that the theme has been loaded

Dynamic Loading

Dynamic loading of themes is a technique that improves performance by loading themes only when they are needed. This approach ensures that your application remains lightweight and fast, as unnecessary CSS files are not loaded upfront.

Using CSS Constructable Style Sheets its easy to load a CSS file and append it dynamically in a way that can be reused in memory (usefull for Shadow DOM). Here’s how you can implement dynamic theme loading in JavaScript:

async function loadTheme(value) {
  const theme = await import(`./theme.${value}.css`, { with: 'css' }); // with syntax is relativly new, check browser support or your build tooling
  const sheet = new CSSStyleSheet();
  sheet.replaceSync(theme.default);
  document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
}

This function dynamically imports the CSS file for the selected theme. The import() function loads the CSS file only when the user requests it. Then a new CSSStyleSheet is created to hold the imported styles. The newly created stylesheet is added to the document, applying the styles immediately.

This method allows us to programatically load themes when needed, reducing the initial load time of the application and improving performance.

Detecting Dynamic Loading

Sometimes it may be nessesary to know when a theme has been loaded. One way to detect a theme loading is by listening for custom animation events triggered by the keyframe animations defined in the theme’s CSS file.

Here’s how you can set up your CSS files to trigger these events:

/* theme.dark.css */
:root {
  --theme: dark;
}

@keyframes theme-dark { }

:root {
  animation: theme-dark;
}
/* theme.light.css */
:root {
  --theme: light;
}

@keyframes theme-light { }

:root {
  animation: theme-light;
}

Each theme defines an empty keyframe animation (theme-dark or theme-light) and applies this animation to the root element. This setup allows us to detect when the theme has been fully loaded by listening for the animationstart event.

Here’s the JavaScript code to detect when a theme has been dynamically loaded:

function onThemeLoad(value, fn) {
  document.documentElement.addEventListener('animationstart', e => {
    if (e.animationName === `theme-${value}`) {
      fn(e);
    }
  });
}

onThemeLoad('dark', e => console.log(e));

This function adds an event listener to the root element, waiting for the animationstart event. When the event is triggered, it checks if the animation name matches the expected theme. If it does, the callback function is executed, confirming that the theme has been successfully loaded.

Conclusion

Managing CSS themes in a design system requires careful consideration of both performance and user experience. By detecting loaded themes through CSS variables, dynamically loading themes only when necessary, and detecting theme application through animation events, you can build a flexible and efficient theming system. This approach not only improves the overall performance of your application but also ensures that users have a smooth and responsive experience when switching between themes. Check out the live demo below to see these techniques in action!

View Demo Code   
Twitter Facebook LinkedIn Email
 

No spam. Short occasional updates on Web Development articles, videos, and new courses in your inbox.

Related Posts

CSS

Flow Charts with CSS Anchor Positioning

Learn how to use CSS Anchor Positioning to create flow charts and diagram with just CSS.

Read Article
CSS

Dynamic Contrast Layers with CSS Style Queries

Learn how to create contrasting layers with CSS style queries ensuring your UI is always the right contrast ratio.

Read Article
Web Components

CSS Container Queries in Web Components

Learn how to use CSS Container Queries in Web Components to create reusable and responsive UI.

Read Article