Implementing Light and Dark Mode Theme for a Web Application using Constructible Stylesheets
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!