CSS custom properties (CSS variables) for dynamic theming

Alex Chang Feb 2026
2 tabs
// Theme switching with CSS variables
const themeToggle = document.getElementById('theme-toggle');
const root = document.documentElement;

// Load saved theme
const savedTheme = localStorage.getItem('theme') || 'light';
root.setAttribute('data-theme', savedTheme);

themeToggle.addEventListener('click', () => {
  const currentTheme = root.getAttribute('data-theme');
  const newTheme = currentTheme === 'light' ? 'dark' : 'light';

  root.setAttribute('data-theme', newTheme);
  localStorage.setItem('theme', newTheme);
});

// Dynamic color adjustment
function setAccentColor(color) {
  root.style.setProperty('--color-primary', color);
  root.style.setProperty('--color-primary-dark', adjustBrightness(color, -20));
  root.style.setProperty('--color-primary-light', adjustBrightness(color, 20));
}

function adjustBrightness(color, percent) {
  const num = parseInt(color.replace('#', ''), 16);
  const amt = Math.round(2.55 * percent);
  const R = (num >> 16) + amt;
  const G = (num >> 8 & 0x00FF) + amt;
  const B = (num & 0x0000FF) + amt;
  return '#' + (
    0x1000000 +
    (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
    (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
    (B < 255 ? B < 1 ? 0 : B : 255)
  ).toString(16).slice(1);
}

// Get computed CSS variable value
const primaryColor = getComputedStyle(root).getPropertyValue('--color-primary');
console.log('Primary color:', primaryColor);

// Create color scheme from single color
function createColorScheme(baseColor) {
  const colors = {
    primary: baseColor,
    light: adjustBrightness(baseColor, 20),
    dark: adjustBrightness(baseColor, -20),
    contrast: getContrastColor(baseColor)
  };

  Object.entries(colors).forEach(([name, value]) => {
    root.style.setProperty(`--color-${name}`, value);
  });
}

function getContrastColor(hexColor) {
  const r = parseInt(hexColor.substr(1, 2), 16);
  const g = parseInt(hexColor.substr(3, 2), 16);
  const b = parseInt(hexColor.substr(5, 2), 16);
  const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
  return (yiq >= 128) ? '#000000' : '#ffffff';
}
2 files · javascript, css Explain with highlit

CSS custom properties define reusable values with --variable-name syntax and access with var(--variable-name). I scope variables at :root for global access or within selectors for local scope. Variables cascade and inherit unlike preprocessor variables. The var() function accepts fallback values like var(--color, blue). Custom properties enable runtime theme switching with JavaScript. I organize design tokens as CSS variables for colors, spacing, typography. Using @property rule adds type checking and animation support. The env() function accesses environment variables like safe area insets. CSS variables work with calc() for dynamic calculations. They're perfect for responsive values, dark mode, and component variants.