CSS Custom Properties and Variables

Introduction to CSS Variables

CSS Custom Properties, commonly known as CSS Variables, provide a powerful way to create reusable values in your stylesheets. Unlike preprocessor variables (like those in Sass or Less), CSS Variables are:

CSS Variables are like design tokens or configuration values for your stylesheets. They allow you to define a value once and reference it throughout your CSS, making changes easier and more consistent.

graph TD A[CSS Custom Properties] --> B[Definition] A --> C[Usage] A --> D[Manipulation] B --> E["--property-name: value;"] C --> F["var(--property-name)"] D --> G["JavaScript: element.style.setProperty()"]

The Syntax of CSS Variables

Defining Custom Properties

CSS Variables are defined using a double-hyphen prefix (--) followed by a name you choose:

:root {
  --primary-color: #3498db;
  --secondary-color: #2ecc71;
  --text-color: #333333;
  --spacing-unit: 8px;
  --font-family-heading: 'Roboto', sans-serif;
  --border-radius: 4px;
}

In this example, we're defining six variables within the :root selector, which makes them available globally throughout the document. The :root selector targets the root element of the document (typically the <html> element).

Using Custom Properties

To use a CSS Variable, you use the var() function:

.button {
  background-color: var(--primary-color);
  color: white;
  padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
  border-radius: var(--border-radius);
  font-family: var(--font-family-heading);
}

.card {
  border: 1px solid #ddd;
  border-radius: var(--border-radius);
  padding: calc(var(--spacing-unit) * 2);
}

This is similar to how you might reference a constant in programming - you define the value once and then reference it by name wherever you need it.

Fallback Values

The var() function can accept a second parameter that serves as a fallback value if the variable is not defined:

.element {
  /* If --custom-width is not defined, 100% will be used */
  width: var(--custom-width, 100%);
  
  /* You can even use another variable as a fallback */
  color: var(--text-color-secondary, var(--text-color-primary, #000));
}

This is like having a backup plan - if your first choice isn't available, you have alternatives ready.

Naming Conventions

While you can name variables however you want, consistent naming patterns make your code more maintainable:

:root {
  /* Component-specific variables */
  --btn-background: #3498db;
  --btn-color: white;
  
  /* Theme variables */
  --theme-primary: #3498db;
  --theme-secondary: #2ecc71;
  
  /* Semantic variables */
  --color-success: #2ecc71;
  --color-warning: #f1c40f;
  --color-error: #e74c3c;
  
  /* Functional variables */
  --spacing-small: 4px;
  --spacing-medium: 8px;
  --spacing-large: 16px;
}

Good naming conventions make your variables more discoverable and self-documenting, similar to how well-named functions and variables improve code readability in programming.

Scoping and the Cascade

One of the most powerful features of CSS Variables is their ability to follow the cascade and be scoped to specific elements. This allows you to override variables for specific components or states.

Variable Scoping :root { --text-color: black; } .header { --text-color: white; } .button:hover { --text-color: yellow; }

Global Variables

Variables defined in the :root selector are available throughout the document:

:root {
  --primary-color: #3498db;
}

Local Variables

Variables can be defined within any selector, limiting their scope to that element and its descendants:

.card {
  --card-padding: 16px;
  padding: var(--card-padding);
}

.card__header {
  /* This will use the --card-padding variable from the parent */
  padding: var(--card-padding);
}

.sidebar {
  /* This defines a different --card-padding for cards within the sidebar */
  --card-padding: 8px;
}

This local scoping is similar to lexical scoping in programming languages - variables are accessible within their defined scope and any nested scopes.

Component Variations Through Scoping

/* Base button styles with default variables */
.button {
  --button-bg: #3498db;
  --button-color: white;
  background-color: var(--button-bg);
  color: var(--button-color);
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
}

/* Button variations through variable overrides */
.button--success {
  --button-bg: #2ecc71;
}

.button--warning {
  --button-bg: #f1c40f;
  --button-color: #333;
}

.button--danger {
  --button-bg: #e74c3c;
}

This approach allows you to create component variations without writing additional CSS properties, similar to how you might use configuration objects to customize instances of a class in object-oriented programming.

State-Based Variables

Variables can be changed based on state:

.accordion {
  --icon-rotation: 0deg;
}

.accordion.is-open {
  --icon-rotation: 180deg;
}

.accordion__icon {
  transform: rotate(var(--icon-rotation));
  transition: transform 0.3s ease;
}

This creates a clear relationship between the component's state and its visual appearance, similar to how state machines in programming define different visual states.

Practical Use Cases for CSS Variables

Theming and Dark Mode

One of the most powerful applications of CSS Variables is creating theme systems, including dark mode:

/* Define theme colors at the root level */
:root {
  /* Light theme (default) */
  --color-bg: #ffffff;
  --color-text: #333333;
  --color-primary: #3498db;
  --color-secondary: #2ecc71;
  --color-border: #e0e0e0;
  --shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Dark theme override */
.theme-dark {
  --color-bg: #121212;
  --color-text: #f5f5f5;
  --color-primary: #90caf9;
  --color-secondary: #81c784;
  --color-border: #333333;
  --shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}

/* Using the variables throughout the site */
body {
  background-color: var(--color-bg);
  color: var(--color-text);
}

.card {
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow);
}

.button-primary {
  background-color: var(--color-primary);
  color: white;
}

This approach allows you to switch themes by simply adding a class to a container element, usually the body or html element:

// JavaScript to toggle dark mode
const toggleTheme = () => {
  document.body.classList.toggle('theme-dark');
}

You can also respect user preferences for dark mode:

/* Use dark theme when user prefers dark color scheme */
@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #121212;
    --color-text: #f5f5f5;
    --color-primary: #90caf9;
    --color-secondary: #81c784;
    --color-border: #333333;
    --shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
  }
}

This reactive theming is like having a light switch for your website that instantly changes the entire ambiance without rebuilding anything.

Responsive Design Tokens

CSS Variables can adapt to different viewport sizes:

:root {
  /* Base/mobile sizes */
  --font-size-h1: 1.75rem;
  --font-size-h2: 1.375rem;
  --font-size-body: 1rem;
  --spacing-unit: 8px;
  --container-padding: 16px;
}

@media (min-width: 768px) {
  :root {
    /* Tablet sizes */
    --font-size-h1: 2.25rem;
    --font-size-h2: 1.75rem;
    --spacing-unit: 12px;
    --container-padding: 24px;
  }
}

@media (min-width: 1200px) {
  :root {
    /* Desktop sizes */
    --font-size-h1: 2.75rem;
    --font-size-h2: 2rem;
    --spacing-unit: 16px;
    --container-padding: 32px;
  }
}

/* Using the responsive variables */
h1 {
  font-size: var(--font-size-h1);
  margin-bottom: calc(var(--spacing-unit) * 2);
}

.container {
  padding: var(--container-padding);
}

This approach centralizes your responsive adjustments, making it easier to maintain consistent scaling across your site. It's like having a master control panel for all your responsive design decisions.

Component Variants

CSS Variables make it easy to create component variants:

/* Base alert component with default variables */
.alert {
  --alert-color: #3498db;
  --alert-bg: #e3f2fd;
  --alert-border: #bbdefb;
  
  color: var(--alert-color);
  background-color: var(--alert-bg);
  border: 1px solid var(--alert-border);
  border-radius: 4px;
  padding: 12px 16px;
}

/* Alert variants through variable overrides */
.alert--success {
  --alert-color: #2e7d32;
  --alert-bg: #e8f5e9;
  --alert-border: #c8e6c9;
}

.alert--warning {
  --alert-color: #f57f17;
  --alert-bg: #fff8e1;
  --alert-border: #ffecb3;
}

.alert--error {
  --alert-color: #c62828;
  --alert-bg: #ffebee;
  --alert-border: #ffcdd2;
}

This approach reduces code duplication and makes relationships between variants explicit. It's like having a base recipe with variations that only specify what changes.

Calculated Values

CSS Variables can be used in calculations with calc():

:root {
  --spacing-unit: 8px;
  --header-height: 60px;
  --sidebar-width: 250px;
}

.header {
  height: var(--header-height);
  padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
}

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

.main-content {
  margin-left: var(--sidebar-width);
  min-height: calc(100vh - var(--header-height));
  padding: calc(var(--spacing-unit) * 3);
}

/* Grid system based on spacing unit */
.grid {
  display: grid;
  grid-gap: var(--spacing-unit);
  grid-template-columns: repeat(auto-fill, minmax(calc(var(--spacing-unit) * 30), 1fr));
}

This allows you to create derived values that automatically update when the base value changes, similar to formulas in a spreadsheet that recalculate when input values change.

Manipulating CSS Variables with JavaScript

One of the most powerful aspects of CSS Variables is their ability to be manipulated with JavaScript, allowing for dynamic styling without directly modifying individual CSS properties.

Reading CSS Variable Values

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

console.log(primaryColor); // e.g., "#3498db"

Setting CSS Variable Values

// Set a CSS variable on the root element (global)
document.documentElement.style.setProperty('--primary-color', '#ff0000');

// Set a CSS variable on a specific element (local)
const header = document.querySelector('.header');
header.style.setProperty('--header-bg', '#000000');

Interactive User Preferences

CSS Variables can be used to create interactive user preference systems:

<!-- HTML for a color picker -->
<label for="theme-color">Theme Color:</label>
<input type="color" id="theme-color" value="#3498db">

<script>
  // Update theme color when the input changes
  const colorPicker = document.getElementById('theme-color');
  
  colorPicker.addEventListener('input', (e) => {
    // Set the primary color variable
    document.documentElement.style.setProperty('--primary-color', e.target.value);
    
    // Calculate and set secondary colors based on the primary color
    const primaryHsl = hexToHSL(e.target.value);
    
    // Create a darker variant for hover states
    const darkerColor = `hsl(${primaryHsl.h}, ${primaryHsl.s}%, ${primaryHsl.l - 10}%)`;
    document.documentElement.style.setProperty('--primary-color-dark', darkerColor);
    
    // Create a lighter variant for backgrounds
    const lighterColor = `hsl(${primaryHsl.h}, ${primaryHsl.s}%, 95%)`;
    document.documentElement.style.setProperty('--primary-color-light', lighterColor);
  });
  
  // Helper function to convert hex to HSL
  function hexToHSL(hex) {
    // Conversion logic here...
    return { h: 210, s: 70, l: 50 }; // Example return
  }
</script>

This creates a system where users can customize the appearance of the site while maintaining design coherence, similar to how software applications often provide theme customization options.

Real-time Animation and Interaction

CSS Variables can be used to create dynamic animations based on user interaction:

.cursor-follower {
  --mouse-x: 0;
  --mouse-y: 0;
  position: fixed;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: var(--primary-color);
  transform: translate(calc(var(--mouse-x) - 50%), calc(var(--mouse-y) - 50%));
  pointer-events: none;
  opacity: 0.7;
  transition: transform 0.1s ease-out;
}

<script>
  const follower = document.querySelector('.cursor-follower');
  
  document.addEventListener('mousemove', (e) => {
    // Update CSS variables with current mouse position
    follower.style.setProperty('--mouse-x', `${e.clientX}px`);
    follower.style.setProperty('--mouse-y', `${e.clientY}px`);
  });
</script>

This approach separates the interaction logic (tracking mouse position) from the visual rendering (using CSS transforms), creating a clean separation of concerns like the Model-View pattern in software architecture.

Dynamic Layout Control

CSS Variables can be used to control layout dynamically:

<!-- HTML Slider for controlling layout -->
<label for="columns-slider">Columns: <span id="columns-value">3</span></label>
<input type="range" id="columns-slider" min="1" max="6" value="3">

<div class="grid">
  <!-- Grid items -->
</div>

<style>
  :root {
    --grid-columns: 3;
  }
  
  .grid {
    display: grid;
    grid-template-columns: repeat(var(--grid-columns), 1fr);
    grid-gap: 16px;
  }
</style>

<script>
  const slider = document.getElementById('columns-slider');
  const columnsValue = document.getElementById('columns-value');
  
  slider.addEventListener('input', (e) => {
    const columns = e.target.value;
    columnsValue.textContent = columns;
    document.documentElement.style.setProperty('--grid-columns', columns);
  });
</script>

This pattern allows for interactive, user-controlled layouts without requiring complex JavaScript to modify individual element styles.

CSS Variables vs. Preprocessor Variables

While CSS Variables and preprocessor variables (like those in Sass, Less, or Stylus) serve similar purposes, they have key differences that make them suitable for different scenarios.

Feature CSS Variables Preprocessor Variables
Browser Support Requires modern browsers (IE11 not fully supported) Compiled to standard CSS, works everywhere
Runtime Changes Can be changed with JavaScript at runtime Static, determined at compile time
Cascade & Inheritance Follows CSS cascade, can be scoped to elements Global by default in scope of file/module
Computed Values Values can be computed by the browser Values are computed during compilation
Syntax --name: value; and var(--name) $name: value; and $name (Sass)
Conditionals & Functions Limited to what CSS supports Rich programming features like conditionals, loops, functions

When to Use CSS Variables

When to Use Preprocessor Variables

Using Both Together

Many modern projects use both types of variables for their respective strengths:

// Sass file with both types of variables
// Preprocessor variables for build-time configuration
$breakpoint-sm: 576px;
$breakpoint-md: 768px;
$breakpoint-lg: 992px;
$breakpoint-xl: 1200px;

// Generate CSS Variables for runtime use
:root {
  // Base colors
  --color-primary: #3498db;
  --color-secondary: #2ecc71;
  
  // Base sizes
  --spacing-unit: 8px;
  --font-size-base: 16px;
  
  // Generate derived variables from preprocessor variables
  @media (min-width: $breakpoint-md) {
    --spacing-unit: 12px;
    --font-size-base: 18px;
  }
  
  @media (min-width: $breakpoint-lg) {
    --spacing-unit: 16px;
  }
}

// Using preprocessor variables for grid generation
.container {
  max-width: $breakpoint-lg;
  margin: 0 auto;
  
  @media (min-width: $breakpoint-xl) {
    max-width: $breakpoint-xl;
  }
}

// Using CSS Variables for component styling
.button {
  padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
  background-color: var(--color-primary);
  font-size: var(--font-size-base);
}

This approach gives you the best of both worlds - preprocessor variables for static configuration and complex logic, and CSS Variables for runtime theming and responsive adjustments.

Design Tokens and Design Systems

CSS Variables are a perfect fit for implementing design tokens in a design system. Design tokens are named entities that store visual design attributes, creating a bridge between design tools and code.

Creating a Design Token System

:root {
  /* Color Tokens */
  --color-brand-primary: #0066cc;
  --color-brand-secondary: #ff9900;
  
  --color-neutral-100: #ffffff;
  --color-neutral-200: #f8f9fa;
  --color-neutral-300: #e9ecef;
  --color-neutral-400: #dee2e6;
  --color-neutral-500: #adb5bd;
  --color-neutral-600: #6c757d;
  --color-neutral-700: #495057;
  --color-neutral-800: #343a40;
  --color-neutral-900: #212529;
  
  --color-feedback-success: #28a745;
  --color-feedback-warning: #ffc107;
  --color-feedback-error: #dc3545;
  --color-feedback-info: #17a2b8;
  
  /* Typography Tokens */
  --font-family-base: 'Inter', system-ui, sans-serif;
  --font-family-heading: 'Montserrat', system-ui, sans-serif;
  
  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-bold: 700;
  
  --font-size-xs: 0.75rem;    /* 12px */
  --font-size-sm: 0.875rem;   /* 14px */
  --font-size-md: 1rem;       /* 16px */
  --font-size-lg: 1.125rem;   /* 18px */
  --font-size-xl: 1.25rem;    /* 20px */
  --font-size-2xl: 1.5rem;    /* 24px */
  --font-size-3xl: 1.875rem;  /* 30px */
  --font-size-4xl: 2.25rem;   /* 36px */
  
  /* Spacing Tokens */
  --spacing-xs: 0.25rem;      /* 4px */
  --spacing-sm: 0.5rem;       /* 8px */
  --spacing-md: 1rem;         /* 16px */
  --spacing-lg: 1.5rem;       /* 24px */
  --spacing-xl: 2rem;         /* 32px */
  --spacing-2xl: 3rem;        /* 48px */
  --spacing-3xl: 4rem;        /* 64px */
  
  /* Border Tokens */
  --border-radius-sm: 0.125rem;  /* 2px */
  --border-radius-md: 0.25rem;   /* 4px */
  --border-radius-lg: 0.5rem;    /* 8px */
  --border-radius-full: 9999px;
  
  --border-width-thin: 1px;
  --border-width-thick: 2px;
  
  /* Shadow Tokens */
  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
  
  /* Animation Tokens */
  --duration-fast: 150ms;
  --duration-normal: 300ms;
  --duration-slow: 500ms;
  
  --easing-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --easing-accelerate: cubic-bezier(0.4, 0, 1, 1);
  --easing-decelerate: cubic-bezier(0, 0, 0.2, 1);
}

Semantic Aliases

To make the design system more maintainable, you can create semantic aliases that reference the base tokens:

:root {
  /* Base tokens defined here... */
  
  /* Semantic Aliases */
  --color-text-primary: var(--color-neutral-900);
  --color-text-secondary: var(--color-neutral-700);
  --color-text-tertiary: var(--color-neutral-600);
  --color-text-inverse: var(--color-neutral-100);
  
  --color-background-default: var(--color-neutral-100);
  --color-background-subtle: var(--color-neutral-200);
  --color-background-accent: var(--color-brand-primary);
  
  --color-border-default: var(--color-neutral-300);
  --color-border-strong: var(--color-neutral-400);
  
  --font-body: var(--font-size-md)/1.5 var(--font-family-base);
  --font-heading-1: var(--font-weight-bold) var(--font-size-4xl)/1.2 var(--font-family-heading);
  --font-heading-2: var(--font-weight-bold) var(--font-size-3xl)/1.2 var(--font-family-heading);
  --font-heading-3: var(--font-weight-bold) var(--font-size-2xl)/1.3 var(--font-family-heading);
  --font-caption: var(--font-size-sm)/1.4 var(--font-family-base);
  
  --spacing-component-sm: var(--spacing-sm);
  --spacing-component-md: var(--spacing-md);
  --spacing-component-lg: var(--spacing-lg);
  
  --spacing-layout-sm: var(--spacing-lg);
  --spacing-layout-md: var(--spacing-xl);
  --spacing-layout-lg: var(--spacing-2xl);
}

This two-level system creates a separation between the raw values (which rarely change) and their semantic usage in the UI (which might change more frequently). It's like having fundamental elements in chemistry that combine to form different compounds with specific purposes.

Component-Specific Tokens

Components can define their own variables that reference the global tokens:

/* Button Component Tokens */
.button {
  --button-padding-x: var(--spacing-md);
  --button-padding-y: var(--spacing-sm);
  --button-font-size: var(--font-size-md);
  --button-border-radius: var(--border-radius-md);
  --button-transition: background-color var(--duration-fast) var(--easing-standard);
  
  /* Default variant */
  --button-bg: var(--color-brand-primary);
  --button-color: var(--color-neutral-100);
  --button-border-color: var(--color-brand-primary);
  --button-hover-bg: #0055aa; /* Darker variant of primary */
  
  /* Apply component tokens to the element */
  padding: var(--button-padding-y) var(--button-padding-x);
  font-size: var(--button-font-size);
  background-color: var(--button-bg);
  color: var(--button-color);
  border: var(--border-width-thin) solid var(--button-border-color);
  border-radius: var(--button-border-radius);
  transition: var(--button-transition);
}

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

/* Button Variants */
.button--secondary {
  --button-bg: transparent;
  --button-color: var(--color-brand-primary);
  --button-border-color: var(--color-brand-primary);
  --button-hover-bg: rgba(0, 102, 204, 0.1); /* Semi-transparent primary */
}

.button--tertiary {
  --button-bg: transparent;
  --button-color: var(--color-text-primary);
  --button-border-color: transparent;
  --button-hover-bg: var(--color-neutral-200);
}

This approach creates a cohesive design system where components share common design tokens but can be customized as needed. It's similar to how a design system like Material Design has global principles but allows customization for specific applications.

Browser Support and Fallbacks

CSS Variables are well-supported in modern browsers, but older browsers (particularly IE11) do not support them or have limited support. Here are strategies for providing fallbacks:

Feature Detection Approach

/* Fallback for browsers that don't support CSS Variables */
.button {
  background-color: #3498db; /* Fallback color */
  background-color: var(--primary-color, #3498db); /* Modern browsers will use this */
}

/* Using @supports for feature detection */
@supports (--css: variables) {
  /* CSS that only runs in browsers that support variables */
  .theme-dark {
    --primary-color: #90caf9;
  }
}

Modernizr Approach

<!-- Include Modernizr for feature detection -->
<script src="modernizr.js"></script>

<style>
  /* Fallback styles */
  .button {
    background-color: #3498db;
  }
  
  /* Styles for browsers that support CSS Variables */
  .cssvarssupport .button {
    background-color: var(--primary-color, #3498db);
  }
</style>

<script>
  // Modernizr adds classes to the HTML element
  // If CSS Variables are supported, the class 'cssvarssupport' is added
</script>

PostCSS Approach

For a more automated solution, you can use PostCSS with the postcss-custom-properties plugin to generate static fallbacks during build:

/* Your source CSS with variables */
:root {
  --primary-color: #3498db;
}

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

/* After PostCSS processing, becomes: */
:root {
  --primary-color: #3498db;
}

.button {
  background-color: #3498db; /* Static fallback */
  background-color: var(--primary-color); /* For modern browsers */
}

This approach automatically generates fallbacks while still preserving the dynamic nature of CSS Variables in modern browsers.

Progressive Enhancement

A general approach is to treat CSS Variables as progressive enhancement:

  1. Design your site to work with static values first
  2. Add CSS Variables to enhance the experience in modern browsers
  3. Use dynamic features like theming as an enhancement, not a core requirement

This ensures your site works for all users, with an enhanced experience for those with modern browsers.

Best Practices for CSS Variables

Naming Conventions

Organization

Performance Considerations

Maintainability Tips

Practice Activity: Design Token System

Activity Instructions:

  1. Create a design token system using CSS Variables for a hypothetical app or website. Your system should include:
    • Base tokens for colors, typography, spacing, and other fundamental values
    • Semantic tokens that reference the base tokens
    • Component-specific tokens for at least three components
  2. Implement the components using your token system
  3. Create a light and dark theme that switches by changing variables, not by redefining components
  4. Add a JavaScript feature that allows users to customize at least one aspect of your design (like primary color)

Starter Code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Design Token System</title>
  <style>
    /* Base Reset */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    body {
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
      line-height: 1.5;
    }
    
    /* Your Design Token System Goes Here */
    :root {
      /* Base Tokens */
      
      /* Semantic Tokens */
      
      /* Component Tokens */
    }
    
    /* Dark Theme */
    .theme-dark {
      /* Override relevant variables for dark theme */
    }
    
    /* Components */
    .container {
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
    }
    
    header {
      margin-bottom: 20px;
    }
    
    .theme-toggle {
      margin-bottom: 20px;
    }
    
    .color-picker {
      margin-bottom: 20px;
    }
    
    .component-demo {
      margin-bottom: 40px;
    }
    
    /* Component 1: Card */
    .card {
      /* Use component-specific tokens */
    }
    
    /* Component 2: Button */
    .button {
      /* Use component-specific tokens */
    }
    
    /* Component 3: Alert */
    .alert {
      /* Use component-specific tokens */
    }
  </style>
</head>
<body>
  <div class="container">
    <header>
      <h1>Design Token System</h1>
      <p>A demonstration of a design system using CSS Variables</p>
    </header>
    
    <div class="theme-toggle">
      <button id="theme-toggle-btn">Toggle Dark Mode</button>
    </div>
    
    <div class="color-picker">
      <label for="primary-color">Primary Color: </label>
      <input type="color" id="primary-color" value="#3498db">
    </div>
    
    <section class="component-demo">
      <h2>Cards</h2>
      <div class="card">
        <h3>Card Title</h3>
        <p>This is a card component that uses our design tokens for consistent styling.</p>
        <button class="button">Read More</button>
      </div>
    </section>
    
    <section class="component-demo">
      <h2>Buttons</h2>
      <button class="button">Primary Button</button>
      <button class="button button--secondary">Secondary Button</button>
      <button class="button button--tertiary">Tertiary Button</button>
    </section>
    
    <section class="component-demo">
      <h2>Alerts</h2>
      <div class="alert alert--info">This is an informational alert.</div>
      <div class="alert alert--success">This is a success alert.</div>
      <div class="alert alert--warning">This is a warning alert.</div>
      <div class="alert alert--error">This is an error alert.</div>
    </section>
  </div>

  <script>
    // Theme toggle functionality
    const themeToggleBtn = document.getElementById('theme-toggle-btn');
    themeToggleBtn.addEventListener('click', () => {
      document.body.classList.toggle('theme-dark');
    });
    
    // Color picker functionality
    const colorPicker = document.getElementById('primary-color');
    colorPicker.addEventListener('input', (e) => {
      // Update primary color variable
      // Your code here
    });
  </script>
</body>
</html>

Extra Challenge:

Extend your system with:

Conclusion

CSS Custom Properties (CSS Variables) have transformed how we write and organize CSS, offering a powerful way to create more maintainable, dynamic, and flexible stylesheets. By leveraging the cascade, scope, and JavaScript integration of CSS Variables, we can build sophisticated design systems that adapt to user preferences, viewport sizes, and interaction states.

As you continue to develop your CSS skills, consider incorporating CSS Variables into your workflow, particularly for:

In our next lecture, we'll explore CSS Modules and Component-Based Styling, which builds on these concepts to create even more robust approaches to styling modern web applications.