Skip to content

CSS Custom Properties

This lesson explains CSS Custom Properties with clear examples, use cases, and best practices so you can build reusable Web Components confidently.

What Are CSS Custom Properties?

CSS Custom Properties, also called CSS variables, let you define reusable style values with names like --button-bg, --card-padding, or --brand-color.

In Web Components, CSS Custom Properties are especially useful because they can pass through Shadow DOM boundaries and allow consumers to theme a component without changing its internal CSS.

Concept Description
CSS Custom Property A reusable CSS variable that starts with --
var() Reads the value of a custom property
Fallback Value Default value used when the variable is not provided
Shadow DOM Support Custom properties can inherit into Shadow DOM
Main Benefit Theme Web Components without exposing internal CSS
Common Use Colors, spacing, radius, typography, borders, and design tokens

Basic CSS Custom Properties Example

:root {
  --brand-color: #0d6efd;
  --text-color: #1f2937;
}

button {
  background: var(--brand-color);
  color: white;
}

The --brand-color variable stores the color value, and var(--brand-color) applies it wherever needed.

CSS Custom Properties in Web Components

class AppButton extends HTMLElement {
  constructor() {
    super();

    this.attachShadow({ mode: "open" });

    this.shadowRoot.innerHTML = `
      <style>
        button {
          background: var(--app-button-bg, #0d6efd);
          color: var(--app-button-color, white);
          padding: var(--app-button-padding, 0.5rem 1rem);
          border-radius: var(--app-button-radius, 0.5rem);
          border: none;
          cursor: pointer;
        }
      </style>

      <button type="button">
        <slot>Button</slot>
      </button>
    `;
  }
}

customElements.define("app-button", AppButton);
<app-button>Default Button</app-button>

<app-button
  style="--app-button-bg: #198754; --app-button-radius: 2rem;">
  Success Button
</app-button>

The component provides default styles, but the consumer can override them by setting CSS Custom Properties on the custom element.

How CSS Custom Properties Work

Step What Happens
1. Define variable Create a property like --brand-color
2. Use variable Apply it with var(--brand-color)
3. Add fallback Use var(--brand-color, blue) for default value
4. Inherit value Custom properties inherit through the DOM
5. Theme component Override variables from outside the Web Component

Using Fallback Values

.card {
  background: var(--card-bg, white);
  color: var(--card-color, #1f2937);
  padding: var(--card-padding, 1rem);
  border-radius: var(--card-radius, 0.75rem);
}

Fallback values make the component safe. If the consumer does not provide a variable, the default value inside var() is used.

Theming Shadow DOM With Custom Properties

class UserCard extends HTMLElement {
  constructor() {
    super();

    this.attachShadow({ mode: "open" });

    this.shadowRoot.innerHTML = `
      <style>
        .card {
          background: var(--user-card-bg, #ffffff);
          color: var(--user-card-color, #111827);
          border: 1px solid var(--user-card-border, #d1d5db);
          border-radius: var(--user-card-radius, 0.75rem);
          padding: var(--user-card-padding, 1rem);
        }

        .title {
          color: var(--user-card-title-color, #0d6efd);
          font-size: var(--user-card-title-size, 1.25rem);
        }
      </style>

      <article class="card">
        <h2 class="title"><slot name="title">User</slot></h2>
        <div><slot></slot></div>
      </article>
    `;
  }
}

customElements.define("user-card", UserCard);
<user-card
  style="--user-card-bg: #f8fafc; --user-card-title-color: #6610f2;">
  <span slot="title">Profile</span>
  <p>This card is themed from outside Shadow DOM.</p>
</user-card>

CSS Custom Properties are one of the cleanest ways to allow external theming while keeping Shadow DOM styles encapsulated.

Using Custom Properties With :host

this.shadowRoot.innerHTML = `
  <style>
    :host {
      display: inline-block;
      --badge-bg: #e7f1ff;
      --badge-color: #0d6efd;
    }

    .badge {
      background: var(--badge-bg);
      color: var(--badge-color);
      padding: var(--badge-padding, 0.25rem 0.5rem);
      border-radius: var(--badge-radius, 999px);
      font-size: var(--badge-font-size, 0.875rem);
    }
  </style>

  <span class="badge">
    <slot>Badge</slot>
  </span>
`;

The :host selector is useful for defining default component variables inside Shadow DOM.

Global Theme Variables

:root {
  --color-primary: #0d6efd;
  --color-success: #198754;
  --color-danger: #dc3545;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --radius-md: 0.75rem;
}
this.shadowRoot.innerHTML = `
  <style>
    button {
      background: var(--color-primary);
      padding: var(--space-sm) var(--space-md);
      border-radius: var(--radius-md);
      color: white;
      border: none;
    }
  </style>

  <button type="button">
    <slot>Submit</slot>
  </button>
`;

Global design tokens can be defined on :root and reused across many Web Components.

Component-Level Theme Overrides

<style>
  product-card {
    --product-card-bg: #ffffff;
    --product-card-border: #dee2e6;
    --product-card-radius: 1rem;
    --product-card-price-color: #198754;
  }

  product-card.featured {
    --product-card-bg: #fff8e1;
    --product-card-border: #ffc107;
  }
</style>

<product-card></product-card>
<product-card class="featured"></product-card>

You can define default variables for all instances and override specific instances with classes.

CSS Custom Properties vs ::part()

Feature CSS Custom Properties ::part()
Best For Design tokens like color, spacing, radius, font size Styling exposed internal elements
API Style Variable-based styling Selector-based styling
Shadow DOM Friendly Yes, variables inherit into Shadow DOM Yes, only for elements with part
Encapsulation High Controlled exposure
Example --button-bg app-button::part(button)

Reading and Setting Custom Properties With JavaScript

const button = document.querySelector("app-button");

button.style.setProperty("--app-button-bg", "#6610f2");

const bgColor = getComputedStyle(button)
  .getPropertyValue("--app-button-bg")
  .trim();

console.log(bgColor);

JavaScript can update CSS Custom Properties dynamically, which is useful for themes, user preferences, and runtime styling.

CSS Custom Property Naming Patterns

Pattern Example Use
Global token --color-primary Shared across the full application
Component token --app-button-bg Specific to one component
State token --alert-success-bg Style for a specific variant or state
Semantic token --color-surface Meaning-based design system value
Fallback token var(--button-bg, var(--color-primary)) Use component value first, then global value

Fallback Token Chain

button {
  background: var(--app-button-bg, var(--color-primary, #0d6efd));
  color: var(--app-button-color, var(--color-on-primary, white));
  padding: var(--app-button-padding, 0.5rem 1rem);
}

A fallback chain lets the component first check for a component-level variable, then a global design token, and finally a hardcoded safe default.

When to Use CSS Custom Properties

  • You want to theme a Web Component from outside Shadow DOM.
  • You need reusable design tokens for colors, spacing, or typography.
  • You want consumers to customize styles without editing component code.
  • You are building design system components.
  • You need runtime theme switching such as light and dark mode.
  • You want simple styling APIs instead of many JavaScript properties.

Common CSS Custom Property Use Cases

  • Button background, text color, padding, and radius.
  • Card background, border, shadow, and spacing.
  • Modal width, overlay color, and z-index.
  • Form input border, focus color, and error color.
  • Dark mode and brand theme switching.
  • Design tokens shared across many Web Components.

CSS Custom Properties Best Practices

  • Use clear names such as --app-button-bg and --card-radius.
  • Provide fallback values with var().
  • Use global tokens for shared design values.
  • Use component-level tokens for component-specific customization.
  • Prefer semantic names like --color-primary over visual names like --blue.
  • Document all supported custom properties for each component.
  • Avoid exposing too many variables that make the component hard to maintain.

Common CSS Custom Property Mistakes

  • Forgetting that custom property names must start with --.
  • Using var(--name) without a fallback value.
  • Creating too many component variables with unclear names.
  • Using hardcoded colors inside Shadow DOM and blocking theming.
  • Expecting normal outside CSS selectors to style private Shadow DOM elements.
  • Mixing global tokens and component tokens without a clear naming system.
  • Not documenting which custom properties a component supports.

Key Takeaways

  • CSS Custom Properties are reusable CSS variables.
  • They are defined with --name and used with var(--name).
  • They can inherit into Shadow DOM and support component theming.
  • Fallback values make Web Components safer and more reusable.
  • They are perfect for design tokens, themes, and customizable component APIs.

Pro Tip

For Web Components, expose style customization through CSS Custom Properties instead of asking consumers to depend on your internal class names. This keeps Shadow DOM private while still allowing flexible theming.