CSS Variables (Custom Properties) â Complete Guide
In this tutorial, you'll learn about CSS Variables (Custom Properties). We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
CSS variables (officially called custom properties) let you store values in one place and reuse them throughout your stylesheet. Change the variable, and every rule that uses it updates automatically. They make theming, maintenance, and Responsive Design dramatically simpler.
What You'll Learn
By the end of this tutorial, you'll declare CSS variables with -- prefix, access them with var(), understand scope and inheritance, create dark mode themes, use variables in calc() and media queries, and read/write them with JavaScript.
Prerequisite: You should understand CSS selectors and the cascade. CSS Selectors and CSS Cascade cover what you need.
Where This Fits
flowchart LR
A["Positioning & Layout"] --> B["**CSS Variables**"]
B --> C["Flexbox"]
C --> D["Grid"]
D --> E["Responsive Design"]
style B fill:#f97316,stroke:#c2410c,color:#fff
style A fill:#e5e7eb,stroke:#9ca3af,color:#374151
style E fill:#22c55e,stroke:#16a34a,color:#fff
Declaration and Usage
CSS variables are declared with a double dash (--) prefix and accessed with the var() function:
:root {
--primary: #3498db;
--spacing: 16px;
--radius: 8px;
}
.button {
background: var(--primary);
padding: var(--spacing);
border-radius: var(--radius);
}
Why :root? :root is the highest-level element (<html>). Declaring variables there makes them available everywhere â like global variables in programming.
Providing Fallbacks
.button {
/* Use --button-bg if defined, otherwise default to #3498db */
background: var(--button-bg, #3498db);
/* Multiple fallback levels */
padding: var(--spacing, var(--default-spacing, 16px));
}
Why fallbacks matter: If a variable isn't defined (typo, missing declaration), the fallback kicks in. Without a fallback, the property becomes initial (e.g., background disappears).
Scope â Variables Inherit Through the DOM
Variables cascade just like other CSS properties. Declare them at different levels for different scopes:
:root {
--accent: blue; /* Global â available everywhere */
}
.card {
--accent: red; /* Overrides :root for .card and its children */
background: var(--accent); /* red */
}
.card .title {
color: var(--accent); /* red (inherits from .card) */
}
.other {
background: var(--accent); /* blue (from :root â no .card in ancestors) */
}
This is the power of scope: A component can set its own variable values without affecting other components. It's like local vs global variables in JavaScript.
Theming â Dark Mode with Variables
Variables make theming trivial. Define your theme variables once, and change them with a class:
:root {
--bg: #ffffff;
--text: #1a202c;
--primary: #3498db;
--border: #e2e8f0;
}
.dark {
--bg: #1a202c;
--text: #e2e8f0;
--primary: #63b3ed;
--border: #2d3748;
}
body {
background: var(--bg);
color: var(--text);
transition: background 0.3s, color 0.3s;
}
.card {
border: 1px solid var(--border);
}
<body>
<button onclick="document.body.classList.toggle('dark')">Toggle Dark Mode</button>
<!-- Everything updates automatically -->
</body>
No media queries needed for theme switching. Just toggle a class and all variables update instantly.
Variables in calc()
Variables work seamlessly inside calc():
:root {
--gap: 8px;
}
.grid {
display: grid;
gap: var(--gap);
grid-template-columns: 1fr 1fr;
padding: calc(var(--gap) * 2); /* 16px */
}
.card {
width: calc(100% - var(--gap) * 4); /* 100% - 32px */
}
Variables in Media Queries
One pattern that's especially useful: change variables at breakpoints instead of repeating selectors:
:root {
--font-size: 1rem;
--columns: 2;
--gap: 8px;
}
@media (min-width: 768px) {
:root {
--font-size: 1.125rem;
--columns: 3;
--gap: 16px;
}
}
@media (min-width: 1024px) {
:root {
--font-size: 1.25rem;
--columns: 4;
--gap: 24px;
}
}
.grid {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
gap: var(--gap);
font-size: var(--font-size);
}
This keeps your component styles clean â no media queries scattered through .grid styles. All breakpoint logic is centralized in :root.
JavaScript Access â Dynamic Variables
CSS variables can be read and written with JavaScript, enabling runtime theming, user preferences, and interactive effects:
// Read a variable
const primary = getComputedStyle(document.documentElement)
.getPropertyValue('--primary').trim();
// Write a variable
document.documentElement.style.setProperty('--primary', '#e74c3c');
// Live dark mode toggle
function toggleDark() {
const root = document.documentElement;
const isDark = root.style.getPropertyValue('--bg') === '#1a202c';
root.style.setProperty('--bg', isDark ? '#ffffff' : '#1a202c');
root.style.setProperty('--text', isDark ? '#1a202c' : '#e2e8f0');
}
Why this matters: You can save user preferences (dark mode, accent color) to localStorage and restore them on next visit. The theme persists across sessions.
Complete Variable System
:root {
/* Colors */
--primary: #3498db;
--primary-dark: #2980b9;
--danger: #e74c3c;
--success: #27ae60;
--warning: #f39c12;
--bg: #ffffff;
--bg-muted: #f8f9fa;
--text: #1a202c;
--text-muted: #718096;
/* Spacing */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 48px;
/* Typography */
--font-family: system-ui, sans-serif;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.25rem;
/* Layout */
--max-width: 1200px;
--radius: 8px;
--shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
/* Transitions */
--transition-fast: 0.15s ease;
--transition-base: 0.3s ease;
}
Common Variable Mistakes
1. Confusing CSS Variables with Preprocessor Variables
/* â Wrong: Sass/LESS syntax, not valid CSS */
$primary: #3498db;
/* â
Correct: CSS custom properties */
--primary: #3498db;
Sass variables ($var) are compiled away. CSS variables (--var) are live in the browser and can be changed at runtime.
2. Forgetting the var() Function
/* â Wrong: --primary is not a valid value */
.button { background: --primary; }
/* â
Correct: use var() */
.button { background: var(--primary); }
3. Assuming Variables Work in Media Query Conditions
/* â Wrong: can't use var() in a media query condition */
@media (min-width: var(--breakpoint)) { ... }
/* â
Correct: variables in values, not conditions */
@media (min-width: 768px) {
:root { --columns: 3; }
}
4. Not Providing Fallbacks
/* â Risky: if --btn-bg is undefined, background becomes initial (transparent) */
.btn { background: var(--btn-bg); }
/* â
Safe: fallback ensures something shows */
.btn { background: var(--btn-bg, #3498db); }
5. Over-scoping Variables
/* â Variables on a deep child are useless elsewhere */
.card .footer .inner {
--color: red;
}
/* â
Declare at the right level for the scope you need */
.card {
--color: red; /* Available to .card and all children */
}
Try It Yourself
Dark mode demo with variables:
Common Mistakes Beginners Make
1. Skipping the Fundamentals
Many beginners jump straight to advanced topics without mastering the basics. Take time to understand the core concepts before moving on.
2. Not Practicing Enough
Reading tutorials without writing code leads to shallow understanding. Code along with every example and experiment on your own.
3. Ignoring Error Messages
Error messages tell you exactly what went wrong. Read them carefully â they usually point to the line and type of issue.
4. Copy-Pasting Without Understanding
It's tempting to copy code from tutorials, but typing it yourself and understanding each line builds real skill.
5. Giving Up Too Early
Every developer hits frustrating bugs. Take breaks, ask for help, and remember that struggling is part of learning.
Practice Questions
Q1: How do you declare a CSS variable and how do you access it?
{{< details "Show answer" >}}
Declare with --name: value; (e.g., --primary: blue;). Access with var(--name) (e.g., color: var(--primary);).
{{< /details >}}
Q2: What scope do variables declared in :root have?
{{< details "Show answer" >}}
Global scope â they're available to all elements on the page. Like global variables in programming.
{{< /details >}}
Q3: How does scoping work with CSS variables? {{< details "Show answer" >}} Variables cascade and inherit. A child element can declare its own variable value, which overrides the parent's value for that element and its descendants only. {{< /details >}}
Q4: How do you provide a fallback value in var()?
{{< details "Show answer" >}}
var(--name, fallback-value) â if --name is not defined, fallback-value is used instead.
{{< /details >}}
Q5: How do you change a CSS variable with JavaScript?
{{< details "Show answer" >}}
document.documentElement.style.setProperty('--name', 'value') â this changes it inline, which has the highest specificity.
{{< /details >}}
Challenge
Create a themeable component library using CSS variables:
- Button, Card, Badge, Alert components
- All colors, spacing, radii driven by variables
- A dark theme class that changes everything
- A "Customize" form that lets the user pick the primary color with an
<input type="color">
FAQ
FAQ
What's Next?
Now learn flexbox and grid:
What's Next
Congratulations on completing this Css Variables tutorial! Here's where to go from here:
- Practice daily â Consistency is more important than long study sessions
- Build a project â Apply what you learned by building something real
- Explore related topics â Check out other tutorials in the same category
- Join the community â Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro