Styling Web Components
In this lesson, we delve into the intricacies of styling Web Components. Given their encapsulated nature, Web Components introduce a unique approach to CSS styling that differs from traditional web development. This session aims to cover how to effectively style your custom elements, ensuring they are both visually appealing and functionally robust.
Understanding Shadow DOM’s Styling Scopes
The Shadow DOM provides style encapsulation for Web Components, meaning the CSS defined inside a component doesn’t leak out, and external styles don’t interfere with the component’s internal styles. However, this encapsulation presents challenges when you wish to style components consistently across a project or need some degree of styling flexibility.
Internal Styles
To define styles within your Web Component, you can include a <style>
element directly within the component’s template. These styles apply only to the component’s shadow DOM, ensuring encapsulation:
class MyButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = `
<style>
button { background-color: blue; color: white; padding: 10px 20px; border: none; border-radius: 5px; }
button:hover { background-color: darkblue; }
<\/style>
<button><slot><\/slot><\/button>
`;
}
}
customElements.define('my-button', MyButton);
CSS Custom Properties for Theming
While the Shadow DOM’s encapsulation is beneficial for style isolation, it can make theming and styling customization challenging. CSS Custom Properties (also known as CSS variables) provide a solution, offering a way to define values that can be reused throughout your document and penetrate the Shadow DOM boundaries:
class MyThemedButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = `
<style>
button {
background-color: var(--button-bg, blue);
color: var(--button-color, white);
padding: 10px 20px;
border: none;
border-radius: 5px;
}
button:hover {
background-color: var(--button-hover-bg, darkblue);
}
<\/style>
<button><slot><\/slot><\/button>
`;
}
}
customElements.define('my-themed-button', MyThemedButton);
In this example, var(--button-bg, blue)
attempts to use the --button-bg
custom property for the button’s background color, defaulting to blue if --button-bg
is not defined elsewhere in the document.
Styling Slotted Content
Content passed to a component via slots can be styled from the outside. However, you may also want to define default styles for slotted content within your component. This is where the ::slotted()
pseudo-element comes into play:
class MySlottedButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = `
<style>
::slotted(span) {
font-weight: bold;
color: var(--button-color, white);
}
<\/style>
<button><slot><\/slot><\/button>
`;
}
}
customElements.define('my-slotted-button', MySlottedButton);
Practical examples
Let’s dive into two practical examples to illustrate the styling techniques for Web Components, focusing on using internal styles, CSS custom properties for theming, and styling slotted content.
Example 1: Themed Button Component
This example demonstrates how to create a themed-button
component that uses CSS custom properties to allow for easy theming. This approach enables the button’s colors to be customized through the use of CSS variables defined outside of the component.
ThemedButton.js
- Defining the Component
class ThemedButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = `
<style>
button {
background-color: var(--button-bg, #007bff); /* Default blue */
color: var(--button-color, white);
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: var(--button-hover-bg, #0056b3); /* Darker blue */
}
<\/style>
<button><slot>Button<\/slot></button>
`;
}
}
customElements.define('themed-button', ThemedButton);
Using ThemedButton
in Your HTML
You can use the themed-button
component in your HTML and apply different themes using CSS custom properties:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Themed Button Example</title>
<style>
/* Define custom properties for theming the button */
.dark-theme {
--button-bg: #343a40; /* Dark background */
--button-hov er-bg: #23272b; /* Darker background on hover */
}
<\/style>
<script src="ThemedButton.js" defer></script>
</head>
<body>
<themed-button>Default Theme</themed-button>
<themed-button class="dark-theme">Dark Theme</themed-button>
</body>
</html>
This setup demonstrates how the appearance of themed-button
elements can be customized by simply defining CSS custom properties on their parent or higher in the document.
Example 2: User Profile Card with Slotted Content
In this example, we’ll create a user-profile-card
component that utilizes slots to accept slotted content, demonstrating how to style slotted elements from within the component.
UserProfileCard.js
- Defining the Component
class UserProfileCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = `
<style>
:host {
display: block;
border: 1px solid #ddd;
padding: 20px;
border-radius: 8px;
max-width: 300px;
}
::slotted(h2) {
color: #333;
margin-top: 0;
}
::slotted(p) {
color: #666;
font-size: 14px;
}
<\/style>
<slot name="name"></slot>
<slot name="bio"></slot>
`;
}
}
customElements.define('user-profile-card', UserProfileCard);
Using UserProfileCard
in Your HTML
Now, let’s use the user-profile-card
component and provide slotted content for the name and bio:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>User Profile Card Example</title>
<script src="UserProfileCard.js" defer></script>
</head>
<body>
<user-profile-card>
<h2 slot="name">Kari Nordmann</h2>
<p slot="bio">
Frontend Developer from Oslo with a passion for web accessibility and
user experience.
</p>
</user-profile-card>
</body>
</html>
Summary
Styling Web Components requires a nuanced understanding of the Shadow DOM, CSS Custom Properties, and the ::slotted()
pseudo-element. By leveraging these features, developers can create beautifully styled, encapsulated components that adhere to a consistent theme while remaining flexible enough to adapt to different contexts. This lesson has equipped you with the foundational knowledge to start styling your Web Components effectively, ensuring they are not only functional but also integrate seamlessly into the visual design of your web applications.