Styling Web Components

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:

javascript
	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:

javascript
	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:

javascript
	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
javascript
	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:

html
	<!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
javascript
	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:

html
	<!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.

References