CSS Variables and Custom Properties Guide

CSS Variables (officially called Custom Properties) allow you to define reusable values that can be used throughout your CSS code. They provide powerful ways to create more maintainable, themeable, and dynamic styles.

1. Introduction to CSS Variables

What Are CSS Variables?

CSS Variables are entities defined by developers that contain specific values to be reused throughout a document. They're set using custom property notation (e.g., --main-color: blue;) and are accessed using the var() function.

Benefits of Using CSS Variables

2. Basic Syntax and Usage

Declaring CSS Variables

:root {
  --main-color: #3498db;
  --accent-color: #e74c3c;
  --font-size-base: 16px;
  --spacing: 20px;
}

CSS variables are declared with a double-dash prefix (--) followed by a name. The :root selector is commonly used for global variables, but they can be defined on any element.

Using CSS Variables

.button {
  background-color: var(--main-color);
  color: white;
  padding: var(--spacing);
  font-size: var(--font-size-base);
}

The var() function accesses the value of a custom property. If the custom property is not defined, the browser will use the default value (if provided) or ignore the property.

Providing Fallback Values

/* If --accent-color is not defined, red will be used */
.button-accent {
  background-color: var(--accent-color, red);
}
Basic Button with Variables
.theme-button {
  background-color: var(--button-color);
  color: var(--button-text);
  padding: var(--button-padding);
  border: none;
  border-radius: 4px;
}

3. Scope and Inheritance

Global Scope

Variables defined in the :root selector are globally available to all elements in the document:

:root {
  --global-color: blue;
}

/* Available to any element */
.element {
  color: var(--global-color);
}

Local Scope

Variables can also be defined for specific elements, limiting their scope:

.card {
  --card-color: green; /* Local to .card and its children */
  background-color: var(--card-color);
}

.card .title {
  color: var(--card-color); /* Can access parent's variable */
}

Inheritance and Cascading

Custom properties follow the normal rules of cascading and inheritance in CSS:

Parent element with blue variable

Child element with green variable (overrides parent)

.parent-scope {
  --scope-color: blue;
}

.child-scope {
  --scope-color: green; /* Overrides the parent scope */
}

.scope-element {
  color: var(--scope-color);
}

4. Dynamic Variables with JavaScript

Reading CSS Variables with JavaScript

// Get a variable from root
const rootStyles = getComputedStyle(document.documentElement);
const mainColor = rootStyles.getPropertyValue('--main-color');

console.log(mainColor); // " #3498db"

Setting CSS Variables with JavaScript

// Set a variable on root
document.documentElement.style.setProperty('--main-color', '#9b59b6');

// Set a variable on a specific element
const element = document.querySelector('.my-element');
element.style.setProperty('--element-color', 'green');

Interactive Example - Color Picker

20px

5. Practical Use Cases

Theming and Dark Mode

Theme Example

This is a themed content area that changes based on the selected theme.

/* Light Theme (default) */
:root {
  --theme-bg: #ffffff;
  --theme-text: #333333;
  --theme-primary: #3498db;
  --theme-accent: #e74c3c;
}

/* Dark Theme */
[data-theme="dark"] {
  --theme-bg: #2c3e50;
  --theme-text: #ecf0f1;
  --theme-primary: #3498db;
  --theme-accent: #e74c3c;
}

/* Usage */
.themed-element {
  background-color: var(--theme-bg);
  color: var(--theme-text);
}

Responsive Design

Change variables based on screen size:

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
.responsive-container {
  --columns: 1; /* Default for mobile */
  display: grid;
  grid-template-columns: repeat(var(--columns), 1fr);
}

@media (min-width: 576px) {
  .responsive-container {
    --columns: 2; /* Tablet */
  }
}

@media (min-width: 768px) {
  .responsive-container {
    --columns: 3; /* Desktop */
  }
}

CSS Animation Control

2s
.animation-demo {
  --animation-duration: 2s;
  --animation-timing: ease-in-out;
  animation: slide var(--animation-duration) var(--animation-timing) infinite alternate;
}

@keyframes slide {
  from { transform: translateX(0); }
  to { transform: translateX(200px); }
}

System Preference Detection

This element adapts to your system's color scheme preference (if your browser supports it).

.dark-mode-aware {
  --bg-color: #f8f9fa; /* Light mode default */
  --text-color: #2c3e50;
}

@media (prefers-color-scheme: dark) {
  .dark-mode-aware {
    --bg-color: #2c3e50; /* Dark mode override */
    --text-color: #ecf0f1;
  }
}

6. Advanced Techniques

Calculated Values

Small Text (0.875×)
Medium Text (1×)
Large Text (1.2×)
Extra Large Text (1.44×)
.calculated-demo {
  --base-size: 16px;
  --ratio: 1.2;
  --font-small: calc(var(--base-size) * 0.875);
  --font-medium: var(--base-size);
  --font-large: calc(var(--base-size) * var(--ratio));
  --font-xlarge: calc(var(--base-size) * var(--ratio) * var(--ratio));
}

Fallback Chains

This text is tomato color in modern browsers.
.fallback-demo {
  /* Fallback first */
  color: #ff6347; 
  
  /* Then the variable */
  color: var(--custom-color, tomato);
  
  /* Complex fallback chain */
  color: var(--primary-color, var(--backup-color, var(--default-color, #333)));
}

Combining with CSS Functions

:root {
  --hue: 200;
  --saturation: 70%;
  --lightness: 50%;
}

.element {
  /* HSL based on variables */
  background-color: hsl(var(--hue), var(--saturation), var(--lightness));
  
  /* Lighter variant */
  color: hsl(var(--hue), var(--saturation), calc(var(--lightness) + 30%));
}

7. Best Practices and Organization

Naming Conventions

/* Component based */
.alert {
  --alert-bg: #f8d7da;
  --alert-text: #721c24;
  --alert-border: #f5c6cb;
}

/* Semantic naming */
:root {
  --color-primary: #3498db;
  --color-secondary: #2ecc71;
  --color-danger: #e74c3c;
  --color-warning: #f39c12;
  
  /* Instead of: */
  --blue: #3498db;    /* Avoid color names */
  --green: #2ecc71;   /* that might change */
}

/* Structured naming with BEM-like syntax */
:root {
  --button-primary-bg: #3498db;
  --button-primary-text: white;
  --button-danger-bg: #e74c3c;
  --button-danger-text: white;
}

Organizing Variables

:root {
  /* Colors */
  --color-primary: #3498db;
  --color-secondary: #2ecc71;
  --color-text: #2c3e50;
  --color-background: #f5f7fa;
  
  /* Typography */
  --font-family-base: 'Segoe UI', sans-serif;
  --font-size-base: 16px;
  --font-size-h1: 2rem;
  --font-size-h2: 1.5rem;
  
  /* Spacing */
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  
  /* Effects */
  --border-radius: 4px;
  --box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  --transition: all 0.3s ease;
}

Commenting and Documentation

:root {
  /* Primary Colors
   * Use these for main UI elements
   * ------------------------------- */
  --color-primary: #3498db;   /* Blue: Primary actions, links */
  --color-secondary: #2ecc71; /* Green: Success, confirmation */
  --color-accent: #e74c3c;    /* Red: Errors, destructive actions */
  
  /* Neutral Colors
   * Use for text, backgrounds, borders
   * ---------------------------------- */
  --color-text: #2c3e50;      /* Main text color */
  --color-text-light: #7f8c8d; /* Secondary text, captions */
  --color-border: #ecf0f1;    /* Borders, dividers */
}

8. Browser Support and Polyfills

Browser Support

CSS Variables are well-supported in all modern browsers (Chrome, Firefox, Safari, Edge). Internet Explorer 11 does not support CSS Variables.

Feature Detection

@supports (--custom-property: value) {
  /* Code that uses CSS variables */
  :root {
    --color-primary: blue;
  }
}

@supports not (--custom-property: value) {
  /* Fallback code */
  .button {
    background-color: blue; /* Hardcoded value */
  }
}

JavaScript Polyfill

For older browsers, you can use a polyfill like css-vars-ponyfill:

// Include the polyfill
<script src="css-vars-ponyfill.min.js"></script>

// Initialize
<script>
  cssVars({
    // Options
    onlyLegacy: true, // Only apply to browsers that don't support CSS variables
    variables: {
      // Define variables that don't exist in CSS
      '--color-primary': '#3498db'
    }
  });
</script>

9. Performance Considerations

Performance Impact

Optimization Tips

10. Real-world Examples

Component-Based Design System

/* Base Variables */
:root {
  --color-primary: #3498db;
  --color-text: #2c3e50;
  --spacing-unit: 8px;
  --border-radius: 4px;
}

/* Component Variables */
.card {
  --card-padding: calc(var(--spacing-unit) * 3);
  --card-bg: white;
  --card-shadow: 0 2px 4px rgba(0,0,0,0.1);
  
  padding: var(--card-padding);
  background-color: var(--card-bg);
  box-shadow: var(--card-shadow);
  border-radius: var(--border-radius);
}

.button {
  --button-bg: var(--color-primary);
  --button-color: white;
  --button-padding-y: calc(var(--spacing-unit) * 1.5);
  --button-padding-x: calc(var(--spacing-unit) * 3);
  
  background-color: var(--button-bg);
  color: var(--button-color);
  padding: var(--button-padding-y) var(--button-padding-x);
  border-radius: var(--border-radius);
}

Dynamic Theme Implementation

/* Theme Definitions */
:root {
  /* Default (Light) Theme */
  --theme-bg: #ffffff;
  --theme-surface: #f5f7fa;
  --theme-text: #2c3e50;
  --theme-primary: #3498db;
  --theme-secondary: #2ecc71;
  --theme-error: #e74c3c;
}

/* Dark Theme */
body.dark-theme {
  --theme-bg: #1a1a2e;
  --theme-surface: #16213e;
  --theme-text: #e0e0e0;
  --theme-primary: #4fc3f7;
  --theme-secondary: #67d88e;
  --theme-error: #ff7675;
}

/* High Contrast Theme */
body.high-contrast-theme {
  --theme-bg: #000000;
  --theme-surface: #121212;
  --theme-text: #ffffff;
  --theme-primary: #fcff4d;
  --theme-secondary: #61ff61;
  --theme-error: #ff5252;
}

Responsive Typography System

:root {
  --font-size-base: 16px;
  --line-height: 1.5;
  --scale-ratio: 1.25;
  
  /* Type Scale */
  --font-size-sm: calc(var(--font-size-base) / var(--scale-ratio));
  --font-size-md: var(--font-size-base);
  --font-size-lg: calc(var(--font-size-base) * var(--scale-ratio));
  --font-size-xl: calc(var(--font-size-base) * var(--scale-ratio) * var(--scale-ratio));
  --font-size-xxl: calc(var(--font-size-base) * var(--scale-ratio) * var(--scale-ratio) * var(--scale-ratio));
}

/* Adjust base size at different breakpoints */
@media (min-width: 768px) {
  :root {
    --font-size-base: 18px;
  }
}

@media (min-width: 1200px) {
  :root {
    --font-size-base: 20px;
  }
}