CSS Variables (Custom Properties)
Create reusable values that can be updated dynamically. CSS variables make theming, dark mode, and consistent designs easier to maintain.
💻 Variable Basics
/* Define variable with -- prefix */
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
--font-size-base: 16px;
--spacing-unit: 8px;
}
/* Use variable with var() */
.button {
background: var(--primary-color);
font-size: var(--font-size-base);
padding: var(--spacing-unit);
}
/* Fallback value */
.element {
color: var(--undefined-variable, #000);
/* variable-name fallback */
}
🎯 Defining Variables
/* Global variables (most common) */
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
}
/* Scoped to element */
.card {
--card-padding: 20px;
--card-bg: white;
padding: var(--card-padding);
background: var(--card-bg);
}
/* Child elements inherit variables */
.card .title {
padding: calc(var(--card-padding) / 2);
}
/* Override in specific context */
.card.large {
--card-padding: 40px;
}
/* Media query variables */
:root {
--container-width: 1200px;
}
@media (max-width: 768px) {
:root {
--container-width: 100%;
}
}
🎨 Color System
/* Color palette */
:root {
/* Primary colors */
--color-primary: #007bff;
--color-primary-light: #3395ff;
--color-primary-dark: #0056b3;
/* Secondary colors */
--color-secondary: #6c757d;
--color-success: #28a745;
--color-danger: #dc3545;
--color-warning: #ffc107;
--color-info: #17a2b8;
/* Neutral colors */
--color-white: #ffffff;
--color-black: #000000;
--color-gray-100: #f8f9fa;
--color-gray-200: #e9ecef;
--color-gray-300: #dee2e6;
--color-gray-400: #ced4da;
--color-gray-500: #adb5bd;
--color-gray-600: #6c757d;
--color-gray-700: #495057;
--color-gray-800: #343a40;
--color-gray-900: #212529;
/* Semantic colors */
--color-text: var(--color-gray-900);
--color-bg: var(--color-white);
--color-border: var(--color-gray-300);
}
/* Usage */
.button {
background: var(--color-primary);
color: var(--color-white);
}
.button:hover {
background: var(--color-primary-dark);
}
.text-muted {
color: var(--color-gray-600);
}
📏 Spacing System
/* Spacing scale */
:root {
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
--spacing-3xl: 64px;
}
/* Or based on multiplier */
:root {
--spacing-unit: 8px;
--spacing-1: calc(var(--spacing-unit) * 1); /* 8px */
--spacing-2: calc(var(--spacing-unit) * 2); /* 16px */
--spacing-3: calc(var(--spacing-unit) * 3); /* 24px */
--spacing-4: calc(var(--spacing-unit) * 4); /* 32px */
--spacing-5: calc(var(--spacing-unit) * 5); /* 40px */
--spacing-6: calc(var(--spacing-unit) * 6); /* 48px */
}
/* Usage */
.card {
padding: var(--spacing-3);
margin-bottom: var(--spacing-4);
}
.section {
padding: var(--spacing-5) var(--spacing-3);
}
🔤 Typography System
/* Font families */
:root {
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-serif: Georgia, "Times New Roman", serif;
--font-mono: "SF Mono", Monaco, Consolas, monospace;
}
/* Font sizes */
:root {
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 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 */
--font-size-5xl: 3rem; /* 48px */
}
/* Font weights */
:root {
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
}
/* Line heights */
:root {
--line-height-tight: 1.2;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
--line-height-loose: 2;
}
/* Usage */
body {
font-family: var(--font-sans);
font-size: var(--font-size-base);
line-height: var(--line-height-normal);
}
h1 {
font-size: var(--font-size-4xl);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-tight);
}
code {
font-family: var(--font-mono);
font-size: var(--font-size-sm);
}
🌓 Dark Mode
/* Light theme (default) */
:root {
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--text-primary: #212529;
--text-secondary: #6c757d;
--border-color: #dee2e6;
}
/* Dark theme */
[data-theme="dark"] {
--bg-primary: #212529;
--bg-secondary: #343a40;
--text-primary: #f8f9fa;
--text-secondary: #adb5bd;
--border-color: #495057;
}
/* Or with media query */
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #212529;
--bg-secondary: #343a40;
--text-primary: #f8f9fa;
--text-secondary: #adb5bd;
--border-color: #495057;
}
}
/* Usage */
body {
background: var(--bg-primary);
color: var(--text-primary);
}
.card {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
}
/* Toggle dark mode with JavaScript */
/* document.documentElement.setAttribute('data-theme', 'dark'); */
🎯 Component Variables
/* Button component */
.button {
--button-bg: var(--color-primary);
--button-color: var(--color-white);
--button-padding: var(--spacing-2) var(--spacing-4);
--button-border-radius: 4px;
--button-font-size: var(--font-size-base);
background: var(--button-bg);
color: var(--button-color);
padding: var(--button-padding);
border-radius: var(--button-border-radius);
font-size: var(--button-font-size);
border: none;
cursor: pointer;
}
/* Button variants */
.button.button-secondary {
--button-bg: var(--color-secondary);
}
.button.button-large {
--button-padding: var(--spacing-3) var(--spacing-5);
--button-font-size: var(--font-size-lg);
}
.button.button-small {
--button-padding: var(--spacing-1) var(--spacing-2);
--button-font-size: var(--font-size-sm);
}
/* Card component */
.card {
--card-bg: var(--bg-primary);
--card-padding: var(--spacing-4);
--card-border-radius: 8px;
--card-shadow: 0 2px 8px rgba(0,0,0,0.1);
background: var(--card-bg);
padding: var(--card-padding);
border-radius: var(--card-border-radius);
box-shadow: var(--card-shadow);
}
.card.card-elevated {
--card-shadow: 0 10px 20px rgba(0,0,0,0.15);
}
🔧 JavaScript Integration
/* CSS */
:root {
--dynamic-color: #007bff;
--dynamic-size: 16px;
}
.element {
color: var(--dynamic-color);
font-size: var(--dynamic-size);
}
/* JavaScript - Get variable */
const root = document.documentElement;
const color = getComputedStyle(root).getPropertyValue('--dynamic-color');
console.log(color); // #007bff
/* JavaScript - Set variable */
root.style.setProperty('--dynamic-color', '#dc3545');
/* JavaScript - Remove variable */
root.style.removeProperty('--dynamic-color');
/* Example: Dynamic theme color */
function setThemeColor(color) {
document.documentElement.style.setProperty('--primary-color', color);
}
/* Example: Interactive slider */
const slider = document.querySelector('#size-slider');
slider.addEventListener('input', (e) => {
document.documentElement.style.setProperty(
'--dynamic-size',
`${e.target.value}px`
);
});
🎯 Practical Examples
/* Complete design system */
:root {
/* Colors */
--primary: #007bff;
--secondary: #6c757d;
--success: #28a745;
--danger: #dc3545;
--warning: #ffc107;
--info: #17a2b8;
/* Spacing (8px base) */
--space-1: 8px;
--space-2: 16px;
--space-3: 24px;
--space-4: 32px;
--space-5: 40px;
/* Typography */
--font-family: -apple-system, sans-serif;
--font-size-sm: 14px;
--font-size-base: 16px;
--font-size-lg: 18px;
--font-size-xl: 24px;
/* Borders */
--border-width: 1px;
--border-radius: 4px;
--border-radius-lg: 8px;
--border-color: #dee2e6;
/* Shadows */
--shadow-sm: 0 1px 3px rgba(0,0,0,0.12);
--shadow-md: 0 4px 6px rgba(0,0,0,0.1);
--shadow-lg: 0 10px 20px rgba(0,0,0,0.15);
/* Transitions */
--transition-fast: 150ms;
--transition-base: 300ms;
--transition-slow: 500ms;
}
/* Grid system with variables */
:root {
--grid-columns: 12;
--grid-gap: 20px;
--container-max-width: 1200px;
}
.container {
max-width: var(--container-max-width);
margin: 0 auto;
padding: 0 var(--space-3);
}
.grid {
display: grid;
grid-template-columns: repeat(var(--grid-columns), 1fr);
gap: var(--grid-gap);
}
/* Responsive font sizes */
:root {
--h1-size: clamp(2rem, 5vw, 3rem);
--h2-size: clamp(1.5rem, 4vw, 2rem);
--h3-size: clamp(1.25rem, 3vw, 1.5rem);
}
h1 { font-size: var(--h1-size); }
h2 { font-size: var(--h2-size); }
h3 { font-size: var(--h3-size); }
/* Utility classes with variables */
.mt-1 { margin-top: var(--space-1); }
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.p-1 { padding: var(--space-1); }
.p-2 { padding: var(--space-2); }
.p-3 { padding: var(--space-3); }
.text-primary { color: var(--primary); }
.text-secondary { color: var(--secondary); }
.bg-primary { background: var(--primary); }
/* Theme switcher */
[data-theme="dark"] {
--bg: #1a1a1a;
--text: #ffffff;
--card-bg: #2d2d2d;
}
[data-theme="light"] {
--bg: #ffffff;
--text: #000000;
--card-bg: #f8f9fa;
}
body {
background: var(--bg);
color: var(--text);
transition: background var(--transition-base), color var(--transition-base);
}
.card {
background: var(--card-bg);
}
/* Accessible focus styles */
:root {
--focus-color: #007bff;
--focus-width: 2px;
--focus-offset: 2px;
}
button:focus-visible,
a:focus-visible {
outline: var(--focus-width) solid var(--focus-color);
outline-offset: var(--focus-offset);
}
/* Status indicators */
:root {
--status-success: #28a745;
--status-error: #dc3545;
--status-warning: #ffc107;
--status-info: #17a2b8;
}
.status-badge {
padding: 4px 8px;
border-radius: var(--border-radius);
font-size: var(--font-size-sm);
}
.status-success {
background: var(--status-success);
color: white;
}
.status-error {
background: var(--status-error);
color: white;
}
/* Animations with variables */
:root {
--animation-duration: 300ms;
--animation-easing: cubic-bezier(0.4, 0, 0.2, 1);
}
.animated {
transition: all var(--animation-duration) var(--animation-easing);
}
/* Responsive breakpoints */
:root {
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
}
/* Use with container queries (when supported) */
@container (min-width: 500px) {
.card {
--card-padding: var(--space-4);
}
}
💡 Best Practices
/* 1. Use meaningful names */
/* Bad */
:root {
--color1: #007bff;
--spacing: 8px;
}
/* Good */
:root {
--color-primary: #007bff;
--spacing-unit: 8px;
}
/* 2. Group related variables */
:root {
/* Colors */
--color-primary: #007bff;
--color-secondary: #6c757d;
/* Spacing */
--space-sm: 8px;
--space-md: 16px;
/* Typography */
--font-size-base: 16px;
--font-weight-normal: 400;
}
/* 3. Use fallbacks for critical values */
.element {
color: var(--text-color, #000);
font-size: var(--font-size, 16px);
}
/* 4. Scope variables appropriately */
/* Global in :root */
:root {
--primary-color: #007bff;
}
/* Component-specific */
.card {
--card-padding: 20px;
}
/* 5. Avoid circular dependencies */
/* Bad */
:root {
--size-a: var(--size-b);
--size-b: var(--size-a);
}
/* Good */
:root {
--base-size: 16px;
--size-a: calc(var(--base-size) * 2);
--size-b: calc(var(--base-size) * 3);
}
/* 6. Use calc() with variables */
:root {
--spacing-unit: 8px;
}
.element {
padding: calc(var(--spacing-unit) * 2);
margin: calc(var(--spacing-unit) * 3);
}
/* 7. Document your variables */
:root {
/* Primary brand color - used for buttons, links */
--color-primary: #007bff;
/* Base spacing unit (8px) - multiply for larger spaces */
--spacing-unit: 8px;
}
🎯 Key Takeaways
- --custom-property: Define with double dash prefix
- var(): Use variables with var() function
- :root: Define global variables in :root pseudo-class
- Scoping: Variables cascade and can be overridden
- Fallbacks: Provide fallback values: var(--color, blue)
- JavaScript: Read and update variables dynamically
- Theming: Perfect for dark mode and theme switching
- Inheritance: Variables inherit like other CSS properties