Skip to main content

CSS Custom Properties Guide

Complete guide to CSS variables with practical examples.
Click any code block to copy to clipboard.

What are CSS Custom Properties?

CSS Custom Properties (also called CSS Variables) let you define reusable values in your stylesheets. Unlike preprocessor variables (Sass/Less), they are native to CSS, cascade like other properties, and can be modified with JavaScript at runtime.

Custom properties always start with two dashes (--) and are case-sensitive.

Basic Syntax
/* Defining a custom property */
--property-name: value;

/* Using a custom property */
var(--property-name)
Example
:root {
  --primary-color: #3b82f6;
  --spacing-md: 16px;
  --font-sans: 'Inter', sans-serif;
}

.button {
  background-color: var(--primary-color);
  padding: var(--spacing-md);
  font-family: var(--font-sans);
}

Defining Variables

Global Variables (:root)

Define in :root for global access. The :root selector matches the document's root element (html), giving variables the highest scope.

:root {
  /* Global variables - accessible everywhere */
  --color-primary: #3b82f6;
  --color-secondary: #8b5cf6;
  --color-success: #22c55e;
  --color-error: #ef4444;

  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;

  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.25rem;
}

Scoped Variables

Define within a selector to limit scope. Scoped variables are only available to that element and its descendants.

.card {
  /* Scoped to .card and its descendants */
  --card-padding: 24px;
  --card-radius: 12px;
  --card-shadow: 0 4px 12px rgba(0,0,0,0.1);

  padding: var(--card-padding);
  border-radius: var(--card-radius);
  box-shadow: var(--card-shadow);
}

.card.compact {
  /* Override for compact variant */
  --card-padding: 12px;
  --card-radius: 8px;
}

Naming Conventions

PatternExampleUsage
--color-*--color-primary, --color-text-mutedColors
--spacing-*--spacing-md, --spacing-lgSpacing
--font-*--font-size-lg, --font-weight-boldTypography
--radius-*--radius-sm, --radius-fullBorder radius
--shadow-*--shadow-sm, --shadow-lgBox shadows
--z-*--z-dropdown, --z-modalZ-index layers

Using Variables

var() Syntax

Use var(--property-name) to reference a custom property. Variables can be used anywhere a value is expected.

.element {
  /* Basic usage */
  color: var(--color-primary);

  /* In shorthand properties */
  padding: var(--spacing-sm) var(--spacing-md);

  /* In calc() */
  width: calc(100% - var(--spacing-lg));

  /* Multiple variables */
  box-shadow: var(--shadow-offset-x) var(--shadow-offset-y)
              var(--shadow-blur) var(--shadow-color);
}

Fallback Values

Provide a fallback value after a comma. Used when the variable is not defined or invalid.

.element {
  /* Single fallback value */
  color: var(--color-primary, #3b82f6);

  /* Fallback to another variable */
  color: var(--color-brand, var(--color-primary));

  /* Nested fallbacks */
  color: var(--color-brand, var(--color-primary, blue));

  /* Fallback for complex values */
  font-family: var(--font-custom, 'Helvetica Neue', sans-serif);
}

Nested Fallbacks

Chain fallbacks by nesting var() functions. Useful for building flexible component systems.

/* Fallback chain example */
.button {
  /* Try --btn-bg, then --color-accent, then hardcoded */
  background: var(--btn-bg, var(--color-accent, #8b5cf6));
}

/* This is useful for component theming */
.alert {
  --alert-bg: var(--color-warning, #f59e0b);
  --alert-text: var(--color-warning-text, #78350f);

  background: var(--alert-bg);
  color: var(--alert-text);
}

Inheritance & Cascade

Variables Inherit

Custom properties inherit down the DOM tree, just like color or font-family. Override them in child selectors to create scoped theming.

:root {
  --text-color: #1f2937;
}

body {
  color: var(--text-color); /* Uses #1f2937 */
}

.dark-section {
  --text-color: #f9fafb; /* Override for this subtree */
}

.dark-section p {
  color: var(--text-color); /* Uses #f9fafb */
}

Scoped Theming Example

Define internal variables for a component, then override them for variants. This pattern creates flexible, maintainable components.

/* Base component styles */
.button {
  --btn-bg: var(--color-primary);
  --btn-text: white;
  --btn-padding: 12px 24px;

  background: var(--btn-bg);
  color: var(--btn-text);
  padding: var(--btn-padding);
}

/* Variant: override variables */
.button.secondary {
  --btn-bg: var(--color-secondary);
}

.button.outline {
  --btn-bg: transparent;
  --btn-text: var(--color-primary);
  border: 2px solid var(--color-primary);
}

.button.small {
  --btn-padding: 8px 16px;
}

Live Demo: Scoped Button Variables

Dynamic Values with JavaScript

Setting Variables

Use element.style.setProperty() to set custom properties at runtime.

// Set a CSS variable on an element
element.style.setProperty('--color-primary', '#ef4444');

// Set on document root (global)
document.documentElement.style.setProperty('--color-primary', '#ef4444');

// Remove a variable
element.style.removeProperty('--color-primary');

Reading Variables

Use getComputedStyle() to read the computed value of a variable.

// Get computed value of a CSS variable
const styles = getComputedStyle(element);
const primaryColor = styles.getPropertyValue('--color-primary');
// Returns: '#3b82f6' (the computed value)

// Get from root
const rootStyles = getComputedStyle(document.documentElement);
const spacing = rootStyles.getPropertyValue('--spacing-md');

Theme Switching Example

// Theme switching example
const themeToggle = document.getElementById('theme-toggle');
const root = document.documentElement;

themeToggle.addEventListener('click', () => {
  const isDark = root.classList.toggle('dark');

  if (isDark) {
    root.style.setProperty('--color-bg', '#1f2937');
    root.style.setProperty('--color-text', '#f9fafb');
    root.style.setProperty('--color-primary', '#60a5fa');
  } else {
    root.style.setProperty('--color-bg', '#ffffff');
    root.style.setProperty('--color-text', '#1f2937');
    root.style.setProperty('--color-primary', '#3b82f6');
  }
});

React Integration

useCSSVariable hook
// React hook for CSS variables
function useCSSVariable(name: string, initialValue: string) {
  const [value, setValue] = useState(initialValue);

  useEffect(() => {
    document.documentElement.style.setProperty(name, value);
  }, [name, value]);

  return [value, setValue] as const;
}

// Usage
function App() {
  const [primaryColor, setPrimaryColor] = useCSSVariable(
    '--color-primary',
    '#3b82f6'
  );

  return (
    <input
      type="color"
      value={primaryColor}
      onChange={(e) => setPrimaryColor(e.target.value)}
    />
  );
}

Practical Use Cases

Color Theming System

:root {
  /* Color system */
  --color-primary: #3b82f6;
  --color-primary-hover: #2563eb;
  --color-primary-light: #dbeafe;

  --color-gray-50: #f9fafb;
  --color-gray-100: #f3f4f6;
  --color-gray-200: #e5e7eb;
  --color-gray-700: #374151;
  --color-gray-900: #111827;

  --color-bg: var(--color-gray-50);
  --color-text: var(--color-gray-900);
  --color-text-muted: var(--color-gray-700);
  --color-border: var(--color-gray-200);
}

Spacing Scale

:root {
  /* Spacing scale (8px base) */
  --space-1: 0.25rem;  /* 4px */
  --space-2: 0.5rem;   /* 8px */
  --space-3: 0.75rem;  /* 12px */
  --space-4: 1rem;     /* 16px */
  --space-5: 1.25rem;  /* 20px */
  --space-6: 1.5rem;   /* 24px */
  --space-8: 2rem;     /* 32px */
  --space-10: 2.5rem;  /* 40px */
  --space-12: 3rem;    /* 48px */
  --space-16: 4rem;    /* 64px */
}

.card {
  padding: var(--space-6);
  margin-bottom: var(--space-4);
  gap: var(--space-3);
}

Typography System

:root {
  /* Font families */
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'Fira Code', monospace;

  /* Font sizes */
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;
  --text-3xl: 1.875rem;
  --text-4xl: 2.25rem;

  /* Line heights */
  --leading-tight: 1.25;
  --leading-normal: 1.5;
  --leading-relaxed: 1.75;

  /* Font weights */
  --font-normal: 400;
  --font-medium: 500;
  --font-semibold: 600;
  --font-bold: 700;
}

Dark Mode Toggle

:root {
  --color-bg: #ffffff;
  --color-text: #1f2937;
  --color-text-muted: #6b7280;
  --color-border: #e5e7eb;
  --color-surface: #f9fafb;
  --color-primary: #3b82f6;
}

/* Dark mode override */
:root.dark,
[data-theme="dark"] {
  --color-bg: #111827;
  --color-text: #f9fafb;
  --color-text-muted: #9ca3af;
  --color-border: #374151;
  --color-surface: #1f2937;
  --color-primary: #60a5fa;
}

/* Or use media query */
@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #111827;
    --color-text: #f9fafb;
    /* ... */
  }
}

Component Variants

/* Component with internal variables */
.alert {
  --alert-bg: var(--color-gray-100);
  --alert-text: var(--color-gray-900);
  --alert-border: var(--color-gray-300);
  --alert-icon: var(--color-gray-500);

  background: var(--alert-bg);
  color: var(--alert-text);
  border: 1px solid var(--alert-border);
  padding: var(--space-4);
  border-radius: 8px;
}

.alert-success {
  --alert-bg: #dcfce7;
  --alert-text: #166534;
  --alert-border: #86efac;
  --alert-icon: #22c55e;
}

.alert-error {
  --alert-bg: #fee2e2;
  --alert-text: #991b1b;
  --alert-border: #fca5a5;
  --alert-icon: #ef4444;
}

.alert-warning {
  --alert-bg: #fef3c7;
  --alert-text: #92400e;
  --alert-border: #fcd34d;
  --alert-icon: #f59e0b;
}
Default alert message
Success alert message
Error alert message
Warning alert message

CSS Variables vs Sass/Less Variables

FeatureCSS Custom PropertiesSass/Less Variables
Syntax--var-name: value; / var(--var-name)$var-name: value;
ScopeCascade & inherit through DOMLexical (file/block scope)
Runtime changesYes (via JS or media queries)No (compiled at build time)
Browser supportModern browsers (IE11+)All (compiles to regular CSS)
DevTools inspectionVisible in computed stylesVariables replaced with values
Media query reactiveYesNo (must duplicate code)
Fallback valuesBuilt-in with var(--x, fallback)Manual with @if or maps
Math operationsRequires calc()Native math operators
ThemingRuntime theme switchingBuild-time theme generation

When to Use Each

:root {
  /* Use CSS Custom Properties for: */
  --color-primary: #3b82f6;  /* Theming (runtime changes) */
  --spacing-unit: 8px;       /* Values that might change */
}

/* Use Sass variables for: */
$breakpoint-md: 768px;  /* Build-time constants */
$grid-columns: 12;      /* Config that never changes */
$z-layers: (
  modal: 100,
  dropdown: 50
);  /* Complex data structures */

/* Best of both: Sass variables to CSS variables */
$brand-blue: #3b82f6;
:root {
  --color-primary: #{$brand-blue};
}

Key Takeaway

Use CSS Custom Properties for values that need to change at runtime (theming, dark mode, responsive adjustments). Use Sass/Less variables for build-time configuration, complex data structures, and mathematical operations. Many projects use both together for maximum flexibility.

Browser Support & Tips

Browser Support

  • Chrome 49+ (March 2016)
  • Firefox 31+ (July 2014)
  • Safari 9.1+ (March 2016)
  • Edge 15+ (April 2017)
  • IE - Not supported (use fallbacks)

Pro Tips

  • Always provide fallbacks for critical styles
  • Use meaningful, descriptive names
  • Group related variables with prefixes
  • Document your variable system
  • Keep scope as narrow as practical