cyberangles guide

How to Use CSS Variables for More Dynamic Styles

In the world of web development, maintaining consistent and flexible styles across a project can be challenging. Hard-coded values (like hex colors, spacing units, or font sizes) scattered throughout your CSS often lead to repetition, errors, and difficulty updating styles globally. Enter **CSS Variables** (officially called *Custom Properties*), a powerful feature that lets you define reusable values, update them dynamically, and streamline your styling workflow. CSS variables are not just about reducing repetition—they enable **dynamic styling** that responds to user interactions, viewport changes, or even JavaScript events. Unlike preprocessor variables (e.g., Sass `$variables`), CSS variables are *live* and can be modified at runtime, making them indispensable for modern, interactive web design. In this guide, we’ll dive deep into how CSS variables work, how to use them effectively, and how to leverage their dynamic capabilities to build more maintainable and adaptable stylesheets.

Table of Contents

  1. What Are CSS Variables?
  2. Basic Syntax: Defining and Using Variables
  3. Dynamic Behavior: Updating Variables
  4. Fallback Values
  5. Scoping: Global vs. Local Variables
  6. Practical Use Cases
  7. Best Practices
  8. Troubleshooting Common Issues
  9. Conclusion
  10. Reference

What Are CSS Variables?

CSS Variables (or Custom Properties) are user-defined values that can be reused throughout a stylesheet. They follow a specific syntax and are subject to the CSS cascade, meaning they can be inherited, overridden, and updated dynamically.

Key Benefits:

  • Reduced Repetition: Define values once and reuse them across selectors.
  • Easier Maintenance: Update a single variable to change styles globally.
  • Dynamic Styling: Modify values in real-time with media queries, JavaScript, or user interactions.
  • Theming: Simplify switching between themes (e.g., light/dark mode).

Basic Syntax: Defining and Using Variables

Declaring Variables

CSS variables are declared using a double hyphen (--) prefix, followed by a name (e.g., --primary-color). They must be declared inside a CSS selector, which determines their scope (more on scoping later).

To make a variable globally accessible (usable across the entire stylesheet), declare it in the :root pseudo-class (which targets the root element of the document, typically <html>):

:root {
  /* Global variables */
  --primary-color: #3498db; /* Blue */
  --secondary-color: #2ecc71; /* Green */
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --font-size-base: 16px;
}

Using Variables with var()

To use a variable, reference it with the var() function:

/* Using global variables */
.button {
  background-color: var(--primary-color);
  padding: var(--spacing-sm) var(--spacing-md);
  font-size: var(--font-size-base);
}

.card {
  border: 1px solid var(--secondary-color);
  margin-bottom: var(--spacing-md);
}

Output:
The .button will have a blue background, 8px/16px padding, and 16px font size. The .card will have a green border and 16px bottom margin.

Dynamic Behavior: Updating Variables

Unlike preprocessor variables (e.g., Sass), CSS variables are live and can be updated after the page loads. This makes them ideal for dynamic styling.

With Media Queries

Adjust variables based on viewport size to create responsive designs:

:root {
  --font-size-base: 16px;
}

/* On mobile (screen width < 768px) */
@media (max-width: 768px) {
  :root {
    --font-size-base: 14px; /* Smaller font size on mobile */
  }
}

body {
  font-size: var(--font-size-base); /* Uses updated value on mobile */
}

With Pseudo-Classes/Pseudo-Elements

Modify variables for interactive states like :hover, :focus, or :active:

:root {
  --button-bg: #3498db;
  --button-hover-bg: #2980b9;
}

.button {
  background-color: var(--button-bg);
  transition: background-color 0.3s ease;
}

.button:hover {
  background-color: var(--button-hover-bg); /* Changes on hover */
}

With JavaScript

Use JavaScript to update variables dynamically based on user input, events, or application state. This is where CSS variables truly shine for interactivity.

Example: Updating a variable on button click

<button id="increase-spacing">Increase Spacing</button>
<div class="content">Dynamic spacing content</div>
:root {
  --content-spacing: 16px;
}

.content {
  padding: var(--content-spacing);
  border: 1px solid #ddd;
}
const increaseButton = document.getElementById('increase-spacing');

increaseButton.addEventListener('click', () => {
  // Get the root element
  const root = document.documentElement;
  
  // Get the current value of --content-spacing
  const currentSpacing = getComputedStyle(root).getPropertyValue('--content-spacing');
  
  // Parse the value (remove "px") and add 8px
  const newSpacing = `${parseInt(currentSpacing) + 8}px`;
  
  // Update the variable
  root.style.setProperty('--content-spacing', newSpacing);
});

Clicking the button will increase the .content padding by 8px each time.

Fallback Values

The var() function accepts an optional second parameter: a fallback value to use if the variable is undefined or invalid.

Syntax:

var(--variable-name, fallback-value)

Example:

/* If --accent-color is undefined, use #ff9800 (orange) */
.alert {
  color: var(--accent-color, #ff9800);
}

Fallbacks are useful for:

  • Handling missing variables.
  • Ensuring compatibility with older browsers (though most modern browsers support CSS variables).

Scoping: Global vs. Local Variables

Variables are scoped to the selector in which they are declared.

  • Global Variables: Declared in :root (or <html>) and accessible to all elements.
  • Local Variables: Declared in a specific selector and only accessible to that selector and its descendants.

Example: Local Variable Overriding Global

:root {
  --text-color: #333; /* Global text color */
}

.card {
  --text-color: #666; /* Local text color (overrides global in .card) */
}

/* Uses global --text-color (#333) */
body {
  color: var(--text-color);
}

/* Uses local --text-color (#666) */
.card p {
  color: var(--text-color);
}

Practical Use Cases

Theming (Light/Dark Mode)

CSS variables simplify theme switching by centralizing theme-specific values. Use a data attribute (e.g., data-theme) to toggle themes:

/* Default (light) theme */
:root {
  --bg-color: #ffffff;
  --text-color: #333333;
  --primary-color: #3498db;
}

/* Dark theme */
:root[data-theme="dark"] {
  --bg-color: #1a1a1a;
  --text-color: #f5f5f5;
  --primary-color: #9b59b6;
}

body {
  background-color: var(--bg-color);
  color: var(--text-color);
  transition: background-color 0.3s ease, color 0.3s ease;
}

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

Toggle the theme with JavaScript:

// Switch to dark mode
document.documentElement.setAttribute('data-theme', 'dark');

// Switch back to light mode
document.documentElement.setAttribute('data-theme', 'light');

Responsive Design

Use variables to centralize responsive values (e.g., breakpoints, padding, font sizes) for cleaner, more maintainable code:

:root {
  --breakpoint-sm: 576px;
  --breakpoint-md: 768px;
  --breakpoint-lg: 992px;
  
  --padding-mobile: 16px;
  --padding-desktop: 24px;
}

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

@media (min-width: var(--breakpoint-md)) {
  .container {
    padding: var(--padding-desktop);
  }
}

Component-Level Customization

Let components define their own variables for granular control. For example, a reusable Card component with customizable colors:

/* Base card styles */
.card {
  padding: var(--card-padding, 16px); /* Default padding if not set */
  background: var(--card-bg, #fff);
  border: 1px solid var(--card-border-color, #ddd);
  border-radius: 4px;
}

/* Specialized card variant */
.card.highlight {
  --card-bg: #f8f9fa;
  --card-border-color: #3498db;
  --card-padding: 20px;
}

Now you can customize individual cards without rewriting base styles!

Best Practices

  1. Use Descriptive Names: Name variables by their purpose, not their value (e.g., --primary-color instead of --blue).
  2. Group Related Variables: Organize variables by type (e.g., --color-*, --spacing-*, --font-*):
    :root {
      /* Colors */
      --color-primary: #3498db;
      --color-secondary: #2ecc71;
      
      /* Spacing */
      --spacing-xs: 4px;
      --spacing-sm: 8px;
      
      /* Fonts */
      --font-sans: 'Helvetica', sans-serif;
    }
  3. Avoid Overuse: Don’t create variables for single-use values (e.g., a one-off margin). Reserve them for repeated or dynamic values.
  4. Document Variables: Add comments or a separate variables.css file to explain variable purposes (especially in large projects).
  5. Test for Fallbacks: Ensure fallbacks work for browsers that might not support variables (though this is rare today).

Troubleshooting Common Issues

1. Variable Not Applying? Check for Typos!

Variables are case-sensitive, and typos (e.g., --primay-color instead of --primary-color) are the most common issue.

2. Browser Compatibility

CSS variables are supported in all modern browsers (Chrome, Firefox, Safari, Edge), but not in Internet Explorer. Use tools like PostCSS with postcss-custom-properties if you need IE support (though this sacrifices dynamic updates).

Check current support on caniuse.com.

3. Specificity Issues

Variables inherit from their parent selectors. If a variable isn’t updating, ensure the selector overriding it has sufficient specificity:

/* This may not work if .card has higher specificity */
:root { --card-color: blue; }
.card { --card-color: red; } /* Overrides global for .card elements */

Conclusion

CSS variables revolutionize how we write and maintain styles by combining the power of reusability with dynamic flexibility. Whether you’re building a simple website or a complex web app, they reduce repetition, simplify theming, and enable interactive designs that respond to user input.

By mastering CSS variables, you’ll write cleaner, more maintainable code and unlock new possibilities for dynamic styling.

Reference