🌞

CSS Custom Properties

Dynamic variables that revolutionize how you write CSS. Create flexible design systems, seamless theming, and maintainable stylesheets with native CSS variables.

What Are Custom Properties?

Custom properties (also called CSS variables) let you store values that you can reuse throughout your stylesheet. Unlike preprocessor variables (Sass, Less), these are native to CSS and live in the browser - which means you can change them dynamically with JavaScript!

Styled with CSS Variables
CSS
/* Define custom properties */
:root {
    --primary-color: #2563eb;
    --spacing: 1rem;
    --border-radius: 12px;
}

/* Use them with var() function */
.box {
    background: var(--primary-color);
    padding: var(--spacing);
    border-radius: var(--border-radius);
}

/* Fallback values if property isn't defined */
.element {
    color: var(--text-color, #000000); /* Falls back to black */
}

Syntax Breakdown

  • Defining: --property-name: value; - Custom properties always start with --
  • Using: var(--property-name) - The var() function retrieves the value
  • Fallback: var(--property-name, fallback) - Comma-separated fallback if the property isn't set

Scope & Inheritance

Custom properties follow CSS cascade rules. Define them globally on :root, or scope them to specific elements. Child elements inherit their parent's custom properties.

Global Scope
Local Scope
Inherits from parent
CSS
/* Global scope - available everywhere */
:root {
    --global-color: #2563eb;
}

/* Local scope - only for this element and children */
.card {
    --card-padding: 2rem;
    --card-bg: #f8fafc;

    padding: var(--card-padding);
    background: var(--card-bg);
}

/* Child inherits parent's custom properties */
.card .title {
    /* Can use --card-padding and --card-bg here */
    margin-bottom: calc(var(--card-padding) / 2);
}

Pro tip: Use :root for design system tokens (colors, spacing, typography), and local scope for component-specific values. This creates a clear hierarchy and makes your CSS more maintainable.

Theming Made Easy

Custom properties are perfect for theming. Change a few variables and your entire design transforms. This is how this very website implements its dark/light theme!

Light Theme

Clean and bright, perfect for daytime browsing.

Dark Theme

Easy on the eyes, great for nighttime coding.

CSS
/* Light theme (default) */
:root {
    --bg-color: #ffffff;
    --text-color: #1e293b;
    --border-color: #e2e8f0;
}

/* Dark theme - just override the variables */
[data-theme="dark"] {
    --bg-color: #0f172a;
    --text-color: #f1f5f9;
    --border-color: #334155;
}

/* Components use the variables */
.card {
    background: var(--bg-color);
    color: var(--text-color);
    border: 1px solid var(--border-color);
}

Notice how we only change the custom property values - the component styles stay exactly the same. This is the magic of CSS variables!

Dynamic Updates with JavaScript

Unlike Sass variables that compile away, CSS custom properties exist at runtime. This means JavaScript can modify them on the fly, creating truly interactive experiences.

Dynamic Color

JavaScript
// Get the element
const element = document.querySelector('.color-preview');

// Set a custom property value
element.style.setProperty('--dynamic-color', 'hsl(220, 70%, 55%)');

// Read a custom property value
const color = getComputedStyle(element)
    .getPropertyValue('--dynamic-color');

// Update based on user input
hueSlider.addEventListener('input', (e) => {
    const hue = e.target.value;
    element.style.setProperty('--hue', hue);
});
CSS
.color-preview {
    --hue: 220;
    --saturation: 70%;
    --lightness: 55%;

    background: hsl(var(--hue), var(--saturation), var(--lightness));
    color: white;
}

Advanced Patterns

Custom properties unlock powerful techniques that were impossible before. Here are some advanced patterns that'll level up your CSS game.

Calculations with Custom Properties

1x spacing
2x spacing
3x spacing
4x spacing
CSS
:root {
    --base-spacing: 0.5rem;
}

.spacing-item {
    --multiplier: 1;
    margin-bottom: calc(var(--base-spacing) * var(--multiplier));
    padding: calc(var(--base-spacing) * var(--multiplier));
}

/* Override multiplier inline */
.spacing-item { --multiplier: 2; }

Responsive Design with Custom Properties

CSS
/* Responsive values change in media queries */
:root {
    --container-width: 100%;
    --font-size: 1rem;
    --grid-columns: 1;
}

@media (min-width: 768px) {
    :root {
        --container-width: 90%;
        --font-size: 1.125rem;
        --grid-columns: 2;
    }
}

@media (min-width: 1024px) {
    :root {
        --container-width: 1200px;
        --font-size: 1.25rem;
        --grid-columns: 3;
    }
}

/* Components automatically adapt */
.container {
    width: var(--container-width);
    font-size: var(--font-size);
}

.grid {
    display: grid;
    grid-template-columns: repeat(var(--grid-columns), 1fr);
}

Component API Pattern

CSS
/* Component with customizable API */
.api-button {
    /* Default values */
    --button-color: #2563eb;
    --button-size: 1;

    background: var(--button-color);
    padding: calc(0.75rem * var(--button-size)) calc(1.5rem * var(--button-size));
    font-size: calc(1rem * var(--button-size));
    color: white;
    border: none;
    border-radius: 8px;
    cursor: pointer;
}

/* Hover state lightens the custom color */
.api-button:hover {
    filter: brightness(1.1);
}
HTML
<!-- Customize via inline styles -->
<button class="api-button" style="--button-color: #ef4444; --button-size: 1.2;">
    Large Danger Button
</button>

This pattern creates a clean API for your components. Instead of creating tons of modifier classes (.button-large, .button-danger), you expose custom properties that users can adjust.

Browser Support

CSS custom properties have excellent browser support! They work in all modern browsers since 2016.

Browser Compatibility

  • Chrome/Edge: 49+ ✅
  • Firefox: 31+ ✅
  • Safari: 9.1+ ✅
  • Internet Explorer: Not supported ❌

For IE11 support (if you must), provide fallback values:

CSS
.element {
    /* Fallback for browsers without custom property support */
    color: #2563eb;

    /* Custom property value (overrides fallback in modern browsers) */
    color: var(--primary-color);
}

Real-World Example: Design System

Let's build a complete mini design system using custom properties. This is how professional design systems (like Material Design or Chakra UI) are structured.

CSS
:root {
    /* Color Palette */
    --color-primary-50: #eff6ff;
    --color-primary-100: #dbeafe;
    --color-primary-500: #3b82f6;
    --color-primary-900: #1e3a8a;

    /* Semantic Colors */
    --color-background: var(--color-primary-50);
    --color-text: var(--color-primary-900);
    --color-accent: var(--color-primary-500);

    /* Spacing Scale (8pt grid) */
    --space-1: 0.25rem;   /* 4px */
    --space-2: 0.5rem;    /* 8px */
    --space-3: 0.75rem;   /* 12px */
    --space-4: 1rem;      /* 16px */
    --space-6: 1.5rem;    /* 24px */
    --space-8: 2rem;      /* 32px */

    /* Typography Scale */
    --font-size-sm: 0.875rem;
    --font-size-base: 1rem;
    --font-size-lg: 1.125rem;
    --font-size-xl: 1.25rem;
    --font-size-2xl: 1.5rem;

    /* Border Radius */
    --radius-sm: 4px;
    --radius-md: 8px;
    --radius-lg: 12px;
    --radius-full: 9999px;

    /* Shadows */
    --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
    --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
    --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}

/* Components reference the design tokens */
.card {
    background: var(--color-background);
    color: var(--color-text);
    padding: var(--space-6);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}

.button {
    background: var(--color-accent);
    color: white;
    padding: var(--space-3) var(--space-6);
    font-size: var(--font-size-base);
    border-radius: var(--radius-md);
}

Design System Card

This card uses tokens from our custom property design system.