CSS Variables (Custom Properties)
CSS variables store reusable values. They make CSS more maintainable and enable dynamic theming.
Basic Syntax
Define Variables
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--font-size: 16px;
--spacing: 1rem;
}
Use Variables
.button {
background-color: var(--primary-color);
font-size: var(--font-size);
padding: var(--spacing);
}
.container {
max-width: 1200px;
padding: var(--spacing);
color: var(--primary-color);
}
Fallback Values
Provide default values:
.button {
background-color: var(--primary-color, #3498db);
}
.text {
font-size: var(--font-size, 16px);
}
/* Multiple fallbacks */
.element {
color: var(--text-color, var(--primary-color, black));
}
Scope
Variables can be scoped:
/* Global scope */
:root {
--primary-color: #3498db;
}
/* Component scope */
.card {
--card-bg: white;
--card-padding: 1rem;
background-color: var(--card-bg);
padding: var(--card-padding);
}
.card-dark {
--card-bg: #333;
--card-padding: 1.5rem;
}
Dynamic Variables
Variables can be changed with JavaScript:
:root {
--primary-color: #3498db;
}
// Change variable
document.documentElement.style.setProperty('--primary-color', '#e74c3c');
// Reset variable
document.documentElement.style.removeProperty('--primary-color');
Complete Example: Theme System
/* Light theme (default) */
:root {
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #333333;
--text-secondary: #666666;
--accent-color: #3498db;
--border-color: #dddddd;
--shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
/* Dark theme */
[data-theme="dark"] {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--text-primary: #ffffff;
--text-secondary: #b0b0b0;
--accent-color: #3498db;
--border-color: #444444;
--shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
/* Apply theme */
body {
background-color: var(--bg-primary);
color: var(--text-primary);
font-family: var(--font-family);
}
.card {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1.5rem;
box-shadow: var(--shadow);
}
.button {
background-color: var(--accent-color);
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 5px;
cursor: pointer;
}
// Toggle theme
function toggleTheme() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
}
Complete Example: Design Tokens
/* Spacing scale */
:root {
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
--space-2xl: 3rem;
}
/* Typography scale */
:root {
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
--font-size-3xl: 2rem;
}
/* Colors */
:root {
--color-primary: #3498db;
--color-secondary: #2ecc71;
--color-success: #27ae60;
--color-warning: #f39c12;
--color-danger: #e74c3c;
}
/* Border radius */
:root {
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
}
/* Usage */
.card {
padding: var(--space-lg);
font-size: var(--font-size-base);
border-radius: var(--radius-md);
background-color: var(--color-primary);
}
Complete Example: Responsive Typography
/* Base typography */
:root {
--text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--text-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem);
--text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
--text-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem);
--text-xl: clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem);
--text-2xl: clamp(1.5rem, 1.3rem + 1vw, 2rem);
}
body {
font-size: var(--text-base);
line-height: 1.6;
}
h1 {
font-size: var(--text-2xl);
}
h2 {
font-size: var(--text-xl);
}
p {
font-size: var(--text-base);
}
Complete Example: Component Library
/* Button variants */
:root {
--btn-primary-bg: #3498db;
--btn-primary-hover: #2980b9;
--btn-secondary-bg: #95a5a6;
--btn-secondary-hover: #7f8c8d;
--btn-success-bg: #27ae60;
--btn-success-hover: #219a52;
--btn-padding: 0.75rem 1.5rem;
--btn-border-radius: 5px;
--btn-font-weight: 500;
}
.button {
padding: var(--btn-padding);
border-radius: var(--btn-border-radius);
font-weight: var(--btn-font-weight);
border: none;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
}
.button-primary {
background-color: var(--btn-primary-bg);
color: white;
}
.button-primary:hover {
background-color: var(--btn-primary-hover);
transform: translateY(-2px);
}
.button-secondary {
background-color: var(--btn-secondary-bg);
color: white;
}
.button-secondary:hover {
background-color: var(--btn-secondary-hover);
}
Dynamic Values
Change variables based on state:
.progress {
--progress: 0%;
width: var(--progress);
height: 20px;
background-color: var(--color-primary);
transition: width 0.3s ease;
}
// Update progress
element.style.setProperty('--progress', '75%');
Media Queries with Variables
:root {
--spacing: 1rem;
--font-size: 14px;
}
@media (min-width: 768px) {
:root {
--spacing: 1.5rem;
--font-size: 16px;
}
}
@media (min-width: 992px) {
:root {
--spacing: 2rem;
--font-size: 18px;
}
}
Best Practices
- Use :root for global variables — Available everywhere
- Use meaningful names —
--color-primarynot--blue - Provide fallback values — Always have a default
- Group related variables — Colors, spacing, typography
- Use design tokens — Consistent, reusable values
Common Mistakes
- Not providing fallbacks — Breaks if variable undefined
- Overusing variables — Not everything needs a variable
- Inconsistent naming — Stick to a convention
- Global scope pollution — Use component scope when appropriate
- Not documenting variables — Hard to understand later