CSS Custom Properties: Basics

Introduction to CSS Custom Properties

CSS Custom Properties (also known as CSS Variables) allow you to define reusable values that can be referenced throughout your stylesheet. They provide a way to create more maintainable, flexible, and dynamic CSS.

Key Benefits

  • Reusability: Define values once and reuse them throughout your CSS
  • Maintainability: Update values in one place instead of throughout the stylesheet
  • Scoping: Variables can be scoped to specific elements
  • Dynamic Updates: Values can be modified with JavaScript at runtime
  • Contextual Changes: Values can change based on media queries or other contexts
  • Native CSS: No preprocessor required
Browser Support: CSS Custom Properties are supported in all modern browsers. For older browsers like IE11, consider using a polyfill or providing fallback values.

Basic Syntax and Usage

Defining Custom Properties

Custom properties are defined with a double hyphen (--) prefix and accessed using the var() function:


:root {
  --primary-color: #3498db;
  --secondary-color: #2ecc71;
  --text-color: #333333;
  --font-size-base: 16px;
  --spacing-unit: 8px;
}

/* Using custom properties */
.button {
  background-color: var(--primary-color);
  color: white;
  padding: calc(var(--spacing-unit) * 2);
  font-size: var(--font-size-base);
}

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

The :root Selector

The :root selector is commonly used to define global custom properties. It represents the <html> element but has higher specificity.

Using var() Function

The var() function retrieves the value of a custom property:


/* Basic usage */
.element {
  color: var(--primary-color);
}

/* With fallback value */
.element {
  color: var(--accent-color, #f39c12);
}

/* With nested fallbacks */
.element {
  color: var(--theme-color, var(--primary-color, #3498db));
}
                        

Scope and Inheritance

Cascading Variables

Custom properties follow the CSS cascade and can be overridden in more specific selectors:


:root {
  --text-color: black;
}

.dark-theme {
  --text-color: white;
}

/* Elements within .dark-theme will use white text */
/* Elements outside will use black text */
p {
  color: var(--text-color);
}
                        

Inheritance

Custom properties are inherited by default, meaning child elements will use the value from their parent unless overridden:


.parent {
  --padding: 20px;
}

.child {
  /* Inherits --padding from parent */
  padding: var(--padding);
}

.child.override {
  /* Overrides the inherited value */
  --padding: 10px;
}
                        

Local Scoping

You can define custom properties that only apply to specific components:


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

.card-header {
  padding: var(--card-padding);
  /* These variables are only available within .card and its children */
}
                        

Calculations with Custom Properties

Using calc() with Variables

The calc() function can perform calculations with custom properties:


:root {
  --spacing: 8px;
  --container-width: 1200px;
  --columns: 12;
}

.container {
  max-width: var(--container-width);
  padding: var(--spacing);
}

.column {
  /* Calculate column width based on container width and number of columns */
  width: calc(var(--container-width) / var(--columns) - var(--spacing) * 2);
  margin: var(--spacing);
}

.button {
  /* Double the base spacing for padding */
  padding: calc(var(--spacing) * 2);
}

.button.large {
  /* Triple the base spacing for large buttons */
  padding: calc(var(--spacing) * 3);
}
                        

Complex Calculations

You can create more complex calculations by combining multiple operations:


:root {
  --header-height: 60px;
  --footer-height: 80px;
  --sidebar-width: 250px;
}

.main-content {
  /* Calculate available height by subtracting header and footer */
  min-height: calc(100vh - var(--header-height) - var(--footer-height));
  
  /* Calculate available width by subtracting sidebar */
  width: calc(100% - var(--sidebar-width));
  margin-left: var(--sidebar-width);
}

.grid-item {
  /* Calculate width for a 3-column grid with gutters */
  --gutter: 20px;
  width: calc((100% - var(--gutter) * 2) / 3);
  margin-right: var(--gutter);
}

.grid-item:nth-child(3n) {
  margin-right: 0;
}
                        

Media Queries and Responsive Design

Changing Variables with Media Queries

Custom properties can be updated within media queries to create responsive designs:


:root {
  --container-padding: 15px;
  --heading-size: 24px;
  --body-font-size: 16px;
}

@media (min-width: 768px) {
  :root {
    --container-padding: 30px;
    --heading-size: 32px;
    --body-font-size: 18px;
  }
}

@media (min-width: 1200px) {
  :root {
    --container-padding: 60px;
    --heading-size: 48px;
    --body-font-size: 20px;
  }
}

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

h1 {
  font-size: var(--heading-size);
}

body {
  font-size: var(--body-font-size);
}
                        

Responsive Layouts

Create responsive layouts by changing layout variables at different breakpoints:


:root {
  --columns: 1;
  --gutter: 16px;
}

@media (min-width: 576px) {
  :root {
    --columns: 2;
  }
}

@media (min-width: 992px) {
  :root {
    --columns: 3;
    --gutter: 24px;
  }
}

@media (min-width: 1200px) {
  :root {
    --columns: 4;
    --gutter: 30px;
  }
}

.grid {
  display: grid;
  grid-template-columns: repeat(var(--columns), 1fr);
  gap: var(--gutter);
}
                        

Fallbacks and Browser Support

Providing Fallbacks

There are several ways to handle fallbacks for browsers that don't support custom properties:


/* Method 1: Fallback before var() */
.element {
  color: #3498db; /* Fallback for older browsers */
  color: var(--primary-color);
}

/* Method 2: Fallback within var() */
.element {
  color: var(--primary-color, #3498db);
}

/* Method 3: Feature detection with @supports */
.element {
  color: #3498db;
}

@supports (--css: variables) {
  .element {
    color: var(--primary-color);
  }
}
                        

Using a Polyfill

For broader support, you can use a polyfill like css-vars-ponyfill:


<script src="https://cdn.jsdelivr.net/npm/css-vars-ponyfill@2"></script>
<script>
  cssVars({
    // Options
    onlyLegacy: true, // Only apply to browsers that don't support CSS variables
    watch: true       // Watch for changes
  });
</script>
                        
Note: Polyfills add overhead and may not support all features of native custom properties. Use them only if you need to support older browsers like IE11.

Common Use Cases

Color Management

Centralize your color palette for easy updates:


:root {
  /* Primary palette */
  --color-primary: #3498db;
  --color-primary-light: #5dade2;
  --color-primary-dark: #2980b9;
  
  /* Secondary palette */
  --color-secondary: #2ecc71;
  --color-secondary-light: #58d68d;
  --color-secondary-dark: #27ae60;
  
  /* Neutral colors */
  --color-text: #333333;
  --color-text-light: #666666;
  --color-background: #ffffff;
  --color-border: #dddddd;
  
  /* Semantic colors */
  --color-success: var(--color-secondary);
  --color-error: #e74c3c;
  --color-warning: #f39c12;
  --color-info: var(--color-primary);
}
                        

Typography System

Create a consistent typography system:


:root {
  /* Font families */
  --font-primary: 'Roboto', sans-serif;
  --font-secondary: 'Playfair Display', serif;
  --font-monospace: 'Roboto Mono', monospace;
  
  /* Font sizes */
  --font-size-xs: 12px;
  --font-size-sm: 14px;
  --font-size-md: 16px;
  --font-size-lg: 18px;
  --font-size-xl: 24px;
  --font-size-xxl: 32px;
  
  /* Line heights */
  --line-height-tight: 1.2;
  --line-height-normal: 1.5;
  --line-height-loose: 1.8;
  
  /* Font weights */
  --font-weight-light: 300;
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-bold: 700;
}

body {
  font-family: var(--font-primary);
  font-size: var(--font-size-md);
  line-height: var(--line-height-normal);
  color: var(--color-text);
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-secondary);
  font-weight: var(--font-weight-bold);
}

h1 { font-size: var(--font-size-xxl); }
h2 { font-size: var(--font-size-xl); }
h3 { font-size: var(--font-size-lg); }

code {
  font-family: var(--font-monospace);
  font-size: var(--font-size-sm);
}
                        

Spacing System

Create a consistent spacing system:


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

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

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

.button {
  padding: var(--spacing-sm) var(--spacing-md);
  margin-right: var(--spacing-sm);
}
                        

Next Steps

Now that you understand the basics of CSS Custom Properties, you can explore more advanced topics in our CSS Custom Properties: Advanced guide, including:

  • Manipulating custom properties with JavaScript
  • Creating dynamic themes and color schemes
  • Advanced techniques for responsive design
  • Integration with CSS preprocessors
  • Performance considerations and best practices
Pro Tip: Start incorporating custom properties gradually into your projects. Begin with colors and spacing variables, then expand to more complex use cases as you become comfortable with the syntax and concepts.