Web Components

Simple CSS Custom Property APIs with Web Components

Cory Rylan

- 2 minutes

Web Components provide a built-in Component model for the Web. Web Components also provide CSS style encapsulation via the Shadow DOM APIs. With Shadow DOM the CSS for our Web Components is completely encapsulated to the component and will not leak to the global scope. Global CSS also will not apply to Web Components using Shadow DOM. This is an excellent feature for CSS maintenance as it prevents us from accidentally styling elements unintentionally.

With Shadow DOM, however, there is a tradeoff. We sometimes need to expose a way to customize or theme our Web Component. We can do this with CSS Custom Properties. Custom Properties let us define dynamic variables that can be shared within our CSS. These properties are shared between the global scope and Shadow DOM. Let's take a look at a simple example.

import './style.css';

const template = document.createElement('template');
template.innerHTML = `
<style>
:host {
--background-color: #ccc;
}

div {
background-color: var(--background-color);
padding: 24px;
display: block;
font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
}
</style>

<div>
<slot></slot>
</div>
`
;

class UIBox extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}

customElements.define('ui-box', UIBox);

In this simple box Web Component, we create a div with some basic styles. This div can change its background color via a custom property called --background-color.

ui-box {
--background-color: red;
}

This works as expected, and the box background will be styled red. We can think of CSS Custom Properties as a public API for theming our component. Because of Shadow DOM, the customization is narrowed down to only the properties we choose to expose. However, we can give some more flexibility without expanding our API surface.

import './style.css';

const template = document.createElement('template');
template.innerHTML = `
<style>
:host {
--background: #ccc;
--padding: 24px;
}

div {
background: var(--background);
padding: var(--padding);
display: block;
font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
}
</style>

<div>
<slot></slot>
</div>
`
;

class UIBox extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}

customElements.define('ui-box', UIBox);

In this example, we have replaced --background-color with --background and added a --padding option as well. By changing our property to use a shorthand property, we enable any number of shorthand values rather than being limited to a single value.

ui-box {
--background: content-box radial-gradient(crimson, skyblue);
--padding: 0 12px;
}

While a small change, our theming API is more flexible without increasing the maintenance cost of adding additional CSS Custom Properties. Check out the working demo below!

View Demo Code   
 

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

Related Posts

Web Components

Accessibility with ID Referencing and Shadow DOM

Learn how accessibility behavior can change when using Shadow DOM and Web Components.

Read Article
Lit Web Components

Build your first Web Component with Lit

Learn how to build your own Web Component to use in any JavaScript framework or frontend environment with Lit.

Read Article
Web Components

Using Modern Web Components

Web Components enable UI components that work in any JavaScript framework. Learn how Web Components work in JavaScript frameworks today.

Read Article