CSS variables (custom properties) are an important feature of modern CSS. This article covers various usage patterns and techniques for CSS variables.

Basic Syntax

Declaring Variables

/* Declare global variables in :root */
:root {
  --primary-color: #3498db;
  --secondary-color: #2ecc71;
  --font-size-base: 16px;
  --spacing-unit: 8px;
  --border-radius: 4px;
}

/* Declare local variables in selectors */
.card {
  --card-padding: 20px;
  --card-bg: #ffffff;
}

/* Variable naming conventions */
:root {
  /* Use semantic naming */
  --color-primary: #3498db;
  --color-text: #333333;
  --size-header: 64px;

  /* Use hyphens as separators */
  --font-family-base: system-ui, sans-serif;
  --transition-duration: 0.3s;
}

Using Variables

/* Use the var() function */
.button {
  background-color: var(--primary-color);
  padding: var(--spacing-unit);
  border-radius: var(--border-radius);
}

/* Provide fallback values */
.text {
  color: var(--text-color, #333333);
  font-size: var(--font-size, 16px);
}

/* Nested fallback values */
.element {
  color: var(--theme-color, var(--primary-color, blue));
}

/* Use in calc() */
.container {
  padding: calc(var(--spacing-unit) * 2);
  margin: calc(var(--spacing-unit) * 3);
  width: calc(100% - var(--sidebar-width, 250px));
}

Scoping

Global Scope

/* :root pseudo-class represents the document root */
:root {
  --global-color: #3498db;
  --global-spacing: 16px;
}

/* Available anywhere */
.header {
  background: var(--global-color);
}

.footer {
  padding: var(--global-spacing);
}

Local Scope

/* Variables are valid in declaring selector and descendants */
.card {
  --card-color: #ffffff;
}

.card .title {
  color: var(--card-color);  /* Valid */
}

.other-element {
  color: var(--card-color);  /* Invalid, uses fallback or inherited */
}

Variable Override

:root {
  --button-bg: #3498db;
}

/* Override in specific contexts */
.dark-theme {
  --button-bg: #2980b9;
}

.button {
  background: var(--button-bg);
}

/* Component-level override */
.button-danger {
  --button-bg: #e74c3c;
}

/* Override in media queries */
@media (prefers-color-scheme: dark) {
  :root {
    --button-bg: #2980b9;
  }
}

Theme Systems

Light/Dark Theme

/* Define theme variables */
:root {
  /* Light theme (default) */
  --bg-color: #ffffff;
  --text-color: #333333;
  --border-color: #e0e0e0;
  --shadow-color: rgba(0, 0, 0, 0.1);
}

/* Dark theme */
[data-theme="dark"] {
  --bg-color: #1a1a2e;
  --text-color: #eaeaea;
  --border-color: #3a3a5c;
  --shadow-color: rgba(0, 0, 0, 0.3);
}

/* Follow system preference */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg-color: #1a1a2e;
    --text-color: #eaeaea;
    --border-color: #3a3a5c;
    --shadow-color: rgba(0, 0, 0, 0.3);
  }
}

/* Use variables */
body {
  background-color: var(--bg-color);
  color: var(--text-color);
}

.card {
  border: 1px solid var(--border-color);
  box-shadow: 0 2px 8px var(--shadow-color);
}

Multiple Theme Support

/* Theme variables */
:root {
  --primary: #3498db;
  --primary-light: #5dade2;
  --primary-dark: #2980b9;
}

[data-theme="green"] {
  --primary: #2ecc71;
  --primary-light: #58d68d;
  --primary-dark: #27ae60;
}

[data-theme="purple"] {
  --primary: #9b59b6;
  --primary-light: #af7ac5;
  --primary-dark: #8e44ad;
}

/* Component usage */
.button-primary {
  background: var(--primary);
}

.button-primary:hover {
  background: var(--primary-light);
}

.button-primary:active {
  background: var(--primary-dark);
}

Dynamic Modification

JavaScript Operations

// Get variable value
const root = document.documentElement;
const primaryColor = getComputedStyle(root)
  .getPropertyValue('--primary-color');

// Set variable value
root.style.setProperty('--primary-color', '#e74c3c');

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

// Set on specific element
const card = document.querySelector('.card');
card.style.setProperty('--card-bg', '#f0f0f0');

// Get element's variable
const cardBg = getComputedStyle(card)
  .getPropertyValue('--card-bg');

Theme Switching

// Set theme
function setTheme(theme) {
  document.documentElement.setAttribute('data-theme', theme);
  localStorage.setItem('theme', theme);
}

// Load saved theme
function loadTheme() {
  const saved = localStorage.getItem('theme');
  if (saved) {
    document.documentElement.setAttribute('data-theme', saved);
  }
}

// Detect system preference
function detectSystemTheme() {
  if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
    setTheme('dark');
  }
}

// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)')
  .addEventListener('change', e => {
    setTheme(e.matches ? 'dark' : 'light');
  });

Animation Control

:root {
  --animation-duration: 0.3s;
  --animation-timing: ease-in-out;
}

.element {
  transition: transform var(--animation-duration) var(--animation-timing);
}

/* Reduce motion (accessibility) */
@media (prefers-reduced-motion: reduce) {
  :root {
    --animation-duration: 0.01ms;
  }
}
// Dynamically adjust animation speed
function setAnimationSpeed(speed) {
  const duration = speed === 'fast' ? '0.1s' :
                   speed === 'slow' ? '0.5s' : '0.3s';
  document.documentElement.style.setProperty(
    '--animation-duration',
    duration
  );
}

Practical Patterns

Spacing System

:root {
  --space-unit: 8px;
  --space-xs: calc(var(--space-unit) * 0.5);  /* 4px */
  --space-sm: var(--space-unit);               /* 8px */
  --space-md: calc(var(--space-unit) * 2);     /* 16px */
  --space-lg: calc(var(--space-unit) * 3);     /* 24px */
  --space-xl: calc(var(--space-unit) * 4);     /* 32px */
  --space-2xl: calc(var(--space-unit) * 6);    /* 48px */
}

.card {
  padding: var(--space-md);
  margin-bottom: var(--space-lg);
}

.card-header {
  margin-bottom: var(--space-sm);
}

Color System

:root {
  /* Base colors */
  --color-blue-50: #e3f2fd;
  --color-blue-100: #bbdefb;
  --color-blue-500: #2196f3;
  --color-blue-700: #1976d2;
  --color-blue-900: #0d47a1;

  /* Semantic colors */
  --color-primary: var(--color-blue-500);
  --color-primary-light: var(--color-blue-100);
  --color-primary-dark: var(--color-blue-700);

  /* Status colors */
  --color-success: #4caf50;
  --color-warning: #ff9800;
  --color-error: #f44336;
  --color-info: #2196f3;
}

Typography System

:root {
  /* Font families */
  --font-sans: system-ui, -apple-system, sans-serif;
  --font-serif: Georgia, 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;

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

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

h1 {
  font-size: var(--text-3xl);
  font-weight: var(--font-bold);
  line-height: var(--leading-tight);
}

p {
  font-size: var(--text-base);
  line-height: var(--leading-normal);
}

Responsive Variables

:root {
  --container-width: 100%;
  --sidebar-width: 250px;
  --header-height: 60px;
}

@media (min-width: 768px) {
  :root {
    --container-width: 750px;
    --header-height: 80px;
  }
}

@media (min-width: 1024px) {
  :root {
    --container-width: 980px;
    --sidebar-width: 300px;
  }
}

@media (min-width: 1280px) {
  :root {
    --container-width: 1200px;
  }
}

.container {
  max-width: var(--container-width);
  margin: 0 auto;
}

.sidebar {
  width: var(--sidebar-width);
}

.header {
  height: var(--header-height);
}

Component Design

Customizable Components

/* Button component */
.button {
  /* Component internal variables */
  --btn-padding-x: 1rem;
  --btn-padding-y: 0.5rem;
  --btn-bg: var(--color-primary);
  --btn-color: white;
  --btn-border-radius: var(--border-radius);

  padding: var(--btn-padding-y) var(--btn-padding-x);
  background: var(--btn-bg);
  color: var(--btn-color);
  border-radius: var(--btn-border-radius);
}

/* Size variants */
.button-sm {
  --btn-padding-x: 0.75rem;
  --btn-padding-y: 0.25rem;
  font-size: var(--text-sm);
}

.button-lg {
  --btn-padding-x: 1.5rem;
  --btn-padding-y: 0.75rem;
  font-size: var(--text-lg);
}

/* Color variants */
.button-secondary {
  --btn-bg: var(--color-secondary);
}

.button-danger {
  --btn-bg: var(--color-error);
}

Card Component

.card {
  --card-padding: var(--space-md);
  --card-bg: var(--bg-color);
  --card-border: 1px solid var(--border-color);
  --card-radius: var(--border-radius);
  --card-shadow: 0 2px 8px var(--shadow-color);

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

/* Inline override */
.card.featured {
  --card-border: 2px solid var(--color-primary);
  --card-shadow: 0 4px 16px var(--shadow-color);
}

With SCSS

// SCSS variables for compile time
$breakpoint-md: 768px;
$breakpoint-lg: 1024px;

// CSS variables for runtime
:root {
  --primary: #3498db;
  --spacing: 16px;
}

// Mixed usage
.container {
  padding: var(--spacing);

  @media (min-width: $breakpoint-md) {
    --spacing: 24px;
  }
}

// SCSS function to generate CSS variables
@function css-var($name, $fallback: null) {
  @if $fallback {
    @return var(--#{$name}, #{$fallback});
  }
  @return var(--#{$name});
}

.element {
  color: css-var(primary-color, blue);
}

Best Practices Summary

CSS Variables Best Practices:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Naming Conventions                                │
│   ├── Use semantic naming                          │
│   ├── Keep naming consistent                       │
│   └── Use hyphens as separators                    │
│                                                     │
│   Organization                                      │
│   ├── Global variables in :root                    │
│   ├── Component variables in component selector    │
│   └── Categorize (colors, spacing, typography)     │
│                                                     │
│   Performance                                       │
│   ├── Avoid deeply nested variables               │
│   ├── Batch updates reduce repaints               │
│   └── Use class toggle instead of frequent JS     │
│                                                     │
└─────────────────────────────────────────────────────┘
ScenarioRecommended ApproachExample
Theme switchingdata attribute + variable override[data-theme=“dark”]
Component customizationComponent-level variables—btn-bg
ResponsiveMedia query overrideReassign in @media
Dynamic valuesJS setPropertystyle.setProperty()

Master CSS variables to build flexible and maintainable styling systems.