Skip to content

CSS Custom Properties in Web Components — Complete Guide

DodaTech Updated 2026-06-28 2 min read

In this tutorial, you will learn about CSS Custom Properties in Web Components. We cover key concepts, practical examples, and best practices to help you master this topic.

CSS custom properties cross the Shadow Dom boundary, enabling host pages to theme encapsulated components without breaking style isolation.

What You'll Learn

  • How CSS custom properties inherit into shadow trees
  • How to define theming APIs with custom properties
  • How to provide default values
  • How to create component variants with property sets

Why It Matters

Style Encapsulation prevents CSS from leaking in, but it also prevents external styling. CSS custom properties are the sanctioned way to provide theming hooks for shadow DOM components.

flowchart LR
  A[Host Page] --> B[--primary: blue]
  B --> C[Shadow DOM inherits]
  C --> D[var(--primary)]
  D --> E[Component uses value]

Theme API Pattern

class ThemedAlert extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = `
            <style>
                :host {
                    --alert-bg: #f8d7da;
                    --alert-border: #f5c6cb;
                    --alert-color: #721c24;
                    --alert-padding: 12px;
                    --alert-radius: 4px;
                }
                .alert {
                    background: var(--alert-bg);
                    border: 1px solid var(--alert-border);
                    color: var(--alert-color);
                    padding: var(--alert-padding);
                    border-radius: var(--alert-radius);
                    font-family: sans-serif;
                }
                .alert strong { color: var(--alert-color); }
            </style>
            <div class="alert"><slot></slot></div>
        `;
    }

    static get observedAttributes() {
        return ['type'];
    }

    attributeChangedCallback(name, oldValue, newValue) {
        if (name === 'type') {
            const themes = {
                info: { '--alert-bg': '#d1ecf1', '--alert-border': '#bee5eb', '--alert-color': '#0c5460' },
                success: { '--alert-bg': '#d4edda', '--alert-border': '#c3e6cb', '--alert-color': '#155724' },
                warning: { '--alert-bg': '#fff3cd', '--alert-border': '#ffeeba', '--alert-color': '#856404' },
                error: { '--alert-bg': '#f8d7da', '--alert-border': '#f5c6cb', '--alert-color': '#721c24' }
            };
            const theme = themes[newValue] || themes.error;
            Object.entries(theme).forEach(([prop, value]) => {
                this.style.setProperty(prop, value);
            });
        }
    }
}
customElements.define('themed-alert', ThemedAlert);

Expected output: Users set the type attribute for built-in themes or override individual properties via CSS.

Providing Defaults

Always provide fallback values with var() to ensure the component works without theming.

// Good: default provided
color: var(--text-color, #333);

// Bad: no default, inherits or uses initial
color: var(--text-color);

Common Mistakes

  1. Not providing defaults for custom properties
  2. Using custom properties that do not inherit correctly
  3. Overriding custom properties inside the shadow tree instead of on the host

Practice Questions

  1. Do CSS custom properties cross the shadow boundary? Yes, they inherit into the shadow tree from the host.
  2. How do you provide a default for var()? var(--property-name, default-value).

Mini Project

Build a button component with a complete theming API via CSS custom properties. Support at least 8 properties: --btn-bg, --btn-color, --btn-border, --btn-radius, --btn-padding, --btn-font-size, --btn-hover-bg, --btn-active-transform.

What's Next

Lesson 12: CSS Parts

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro