Implementing Light and Dark Mode Theme for a Web Application using Constructible Stylesheets

Md Meraj
5 min readJul 11, 2023

--

Implementing light and dark mode in a web application has become a popular design trend, providing users with a personalized experience and reducing eye strain. In this article, we’ll explore how to implement light and dark mode for a web application using the Constructible Stylesheet API. We’ll walk through a code example that demonstrates the implementation using JavaScript and HTML, with a focus on sharing styles between web components with Shadow DOM.

Introducing the Constructible Stylesheet API

The Constructible Stylesheet API is a powerful feature introduced in modern web browsers that allows you to dynamically construct and apply CSS stylesheets programmatically. This API provides a convenient way to manage styles and theme changes in a web application.

The Example Code

Let’s dive into the example code that showcases the implementation of light and dark mode using the Constructible Stylesheet API. We have a simple HTML file and JavaScript code that handle the theme change and apply corresponding styles.

HTML Structure

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>

<body>
<h1>Hello world</h1>

<form id="color" autocomplete="off">
<button type="button">
Share
</button>
<p>
The Constructible Stylesheet API is a powerful feature introduced
in modern web browsers that allows you to dynamically construct
and apply CSS stylesheets programmatically.
This API provides a convenient way to manage styles
and theme changes in a web application.
</p>
</div>


<input type="radio" onchange="themeChangeHandler(this)" value="light" name="theme">Light</input>
<input type="radio" onchange="themeChangeHandler(this)" value="dark" name="theme">Dark</input>
</form>

<script src="./script.js"></script>
</body>

</html>

JavaScript Implementation

/** Demo code */
const buttonStyle = new CSSStyleSheet();
const everything = new CSSStyleSheet();

everything.replace(`* { color: #000; }`);

document.adoptedStyleSheets = [everything, buttonStyle];

function themeChangeHandler(event) {
const theme = event.value;

everything
.replace(
`
* {
color: ${theme === "light" ? "#000" : "#fff"};
background-color: ${theme === "light" ? "#fff" : "#000"};
}
`
)
.then(console.log);

buttonStyle
.replace(
`button { background: ${
theme === "light" ? "rgb(239, 239, 239)" : "Blue"
} }`
)
.then(console.log);
}

Explaining the Code

Let’s break down the code and understand how it achieves the light and dark mode functionality.

Constructing Stylesheets

First, we create two instances of CSSStyleSheet: buttonStyle and everything. These objects will hold the styles for our application.

const buttonStyle = new CSSStyleSheet();
const everything = new CSSStyleSheet();

Applying Initial Styles

Next, we set the initial styles for all elements using the everything stylesheet. In this case, we set the default text color to black (#000).

everything.replace(`* { color: #000; }`);

Adopting Stylesheets

To apply the constructed stylesheets to the document, we assign them to the adoptedStyleSheets property. By doing so, the stylesheets become active in the document and affect the rendering of elements.

document.adoptedStyleSheets = [everything, buttonStyle];

Sharing Styles with Shadow DOM

We introduce a custom web component called CustomComponent that utilizes Shadow DOM. Inside the constructor of the component, we create a Shadow Root using this.attachShadow({ mode: "open" }). This allows us to encapsulate the component's markup and styles within the Shadow DOM.

Next, we create a new instance of CSSStyleSheet called componentStyles, which will hold the styles specific to our custom component. We then use componentStyles.replace() to define the component-specific styles.

Finally, we assign the componentStyles stylesheet to the adoptedStyleSheets property of the Shadow Root. This ensures that the styles defined in componentStyles are applied only to the elements within the Shadow DOM of CustomComponent.

class CustomComponent extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "open" });
const componentStyles = new CSSStyleSheet();

componentStyles.replace(`
:host {
display: block;
}
/* Additional styles for your custom component */
`);

shadowRoot.adoptedStyleSheets = [componentStyles, everything, buttonStyle];
}

connectedCallback() {
// these should be sanitized!
const title = this.getAttribute("my-title");
const content = this.getAttribute("my-content");

this.shadowRoot.innerHTML = `
<style>
:host {
display: flex;
flex-direction: column;
border: 1px solid #ddd;
border-radius: 0.2rem;
padding: 1rem;
}
</style>

<div>
${title}
<button>I am inside a shadow-root component, click me!</button>
</div>
`;
}
}

customElements.define("custom-component", CustomComponent);

With this updated code, you can create multiple instances of the CustomComponent and have each instance encapsulate its own styles within its Shadow DOM. The shared styles from everything and buttonStyle will be applied globally, while the component-specific styles defined in componentStyles will be isolated to each instance of CustomComponent.

Handling Theme Change

The themeChangeHandler function is triggered whenever a user selects a different theme (light or dark). It takes the selected theme as an argument.

function themeChangeHandler(event) {
const theme = event.value;

everything
.replace(`
* {
color: ${theme === "light" ? "#000" : "#fff"};
background-color: ${theme === "light" ? "#fff" : "#000"};
}
`)
.then(console.log);

buttonStyle
.replace(`button { background: ${theme === "light" ? "rgb(239, 239, 239)" : "Blue"}; }`)
.then(console.log);
}

Within the themeChangeHandler function, we update the everything stylesheet to change the text color and background color based on the selected theme. If the theme is "light," we set the text color to black (#000) and the background color to white (#fff). If the theme is "dark," we set the text color to white (#fff) and the background color to black (#000).

Similarly, we update the buttonStyle stylesheet to change the background color of the button based on the selected theme. If the theme is "light," we set the background color to a light shade (rgb(239, 239, 239)). If the theme is "dark," we set the background color to blue (Blue).

Final Thoughts

The Constructible Stylesheet API provides a powerful tool for managing styles and theme changes in web applications. By adopting stylesheets and utilizing Shadow DOM, you can create reusable web components that encapsulate their own styles while sharing common styles with the rest of the application.

Feel free to experiment with the example code and tailor it to your specific application requirements. Implementing light and dark mode can enhance the user experience and make your web application visually appealing.

Remember to keep accessibility in mind when designing themes. Consider providing an option for users to manually choose their preferred mode and ensure that the text is readable in both light and dark themes.

Now you have the knowledge to implement light and dark mode using the Constructible Stylesheet API. Start incorporating this feature into your web applications and provide users with a visually pleasing experience!

--

--