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.