Munsif.
AboutExperienceProjectsAchievementsBlogsContact
HomeAboutExperienceProjectsAchievementsBlogsContact
Munsif.

Frontend Developer crafting scalable web applications with modern technologies and clean code practices.

Quick Links

  • About
  • Experience
  • Projects
  • Achievements
  • Blogs
  • Contact

Connect

© 2026 Shaik Munsif. All rights reserved.

Built with Next.js & Tailwind

0%
Welcome back!Continue where you left off
Back to Blogs
CSS

CSS Custom Properties (Variables) Mastery: Dynamic Theming & Beyond

Master CSS Custom Properties from basics to advanced patterns. Covers variable syntax, scoping, fallbacks, design tokens, dark mode theming, JavaScript manipulation, the @property rule, HSL color systems, and component-level variable APIs — all with interactive live previews.

Mar 11, 202624 min read
CSSCustom PropertiesVariablesThemingBeginner Guide
CSS MasteryPart 3 of 10
  • 1. CSS Flexbox Mastery: The Complete Visual Guide to Flexible Layouts
  • 2. CSS Grid Mastery: From Basics to Advanced Layouts
  • 3. CSS Custom Properties (Variables) Mastery: Dynamic Theming & Beyond
  • 4. CSS Animations & Transitions Mastery: From Hover Effects to Keyframe Sequences
  • 5. CSS Selectors Deep Dive: From Basic to :has() & :is()
  • 6. CSS Box Model Mastery: Content, Padding, Border & Margin Explained
  • 7. CSS Responsive Design Mastery: Mobile-First, clamp(), & Container Queries
  • 8. CSS Specificity & Cascade Mastery: How Browsers Resolve Conflicts
  • 9. CSS Modern Layout Techniques: Scroll Snap, Subgrid, Logical Properties & More
  • 10. CSS Pseudo-Elements Mastery: ::before, ::after, Counters & Decorative Techniques

CSS Custom Properties — commonly called CSS Variables — are one of the most transformative features added to CSS. They let you define reusable values that can be updated dynamically, inherited through the DOM, and even changed with JavaScript — all without a preprocessor like Sass or Less.

Think of CSS Custom Properties as named containers that hold CSS values. You define them once, use them everywhere, and when you change the container's value, every place that references it updates automatically.

In this guide, we'll go from basic variable syntax to advanced patterns like dynamic theming, the @property rule, and runtime animation.


1. Defining and Using Custom Properties

Declaring a Variable

Custom properties always start with two dashes (--):

css
:root {
  --primary-color: #3b82f6;
  --spacing: 1rem;
  --font-main: 'Inter', sans-serif;
}

Using a Variable

Use the var() function to reference a custom property:

css
.button {
  background: var(--primary-color);
  padding: var(--spacing);
  font-family: var(--font-main);
}
Live Preview
Using --demo-color
Using --demo-spacing
Using --demo-radius

2. The :root Selector and Scope

Global Variables with :root

The :root selector targets the <html> element and is the conventional place to define global variables:

css
:root {
  --brand-primary: #6366f1;
  --brand-secondary: #ec4899;
  --text-color: #1e293b;
  --bg-color: #ffffff;
}

Scoped Variables

Custom properties follow CSS inheritance. You can redefine a variable on any element, and all its children will use the new value:

css
:root {
  --accent: #3b82f6; /* Global default = blue */
}

.danger-zone {
  --accent: #ef4444; /* Override = red for this section */
}

.button {
  background: var(--accent); /* Uses whatever --accent is in scope */
}
Live Preview
Default (--accent: blue)
Danger (--accent: red)
Success (--accent: green)

Key Insight: This scoping behavior is what makes custom properties far more powerful than Sass variables. Sass variables are compile-time only; CSS custom properties are live in the browser and can be dynamically overridden by any ancestor.


3. Fallback Values

The var() function accepts a fallback as the second argument:

css
.card {
  background: var(--card-bg, #ffffff);
  /* If --card-bg is not defined, use #ffffff */
}

Nested Fallbacks

You can chain fallbacks:

css
.card {
  color: var(--card-text, var(--text-color, #000000));
  /* Try --card-text → then --text-color → then #000000 */
}

Pro Tip: Always provide meaningful fallbacks for custom properties that might not be defined in every context. This makes your components more portable and resilient.


4. CSS Variables vs Preprocessor Variables

FeatureCSS Custom PropertiesSass/Less Variables
Evaluated atRuntime (in the browser)Compile time (build step)
Can be changed dynamically✅ Yes (JS, media queries, hover)❌ No
Cascade & inherit✅ Yes (follow CSS rules)❌ No (flat scope)
Available in DevTools✅ Yes (inspect & modify)❌ No
Require build tool❌ No✅ Yes
Conditional per media query✅ Yes❌ No

5. Building a Design Token System

Design tokens are the "atoms" of your design system — colors, spacing, typography, and shadows defined as CSS custom properties:

css
:root {
  /* Colors */
  --color-primary: #6366f1;
  --color-primary-light: #818cf8;
  --color-primary-dark: #4f46e5;
  --color-surface: #ffffff;
  --color-text: #1e293b;
  --color-text-muted: #64748b;

  /* Spacing Scale */
  --space-1: 0.25rem;  /* 4px */
  --space-2: 0.5rem;   /* 8px */
  --space-3: 0.75rem;  /* 12px */
  --space-4: 1rem;     /* 16px */
  --space-6: 1.5rem;   /* 24px */
  --space-8: 2rem;     /* 32px */

  /* Typography */
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'Fira Code', monospace;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;

  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);

  /* Border Radius */
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-full: 9999px;
}
Live Preview
Design Token Card
This card uses CSS custom properties as design tokens. Every color, spacing, and shadow is defined as a variable.

6. Dark Mode with Custom Properties

This is one of the most popular use cases. Define two sets of values — one for light mode, one for dark mode — and toggle them with a class or media query:

Method 1: Class Toggle

css
:root {
  --bg: #ffffff;
  --text: #1e293b;
  --surface: #f8fafc;
  --border: #e2e8f0;
}

.dark {
  --bg: #0f172a;
  --text: #e2e8f0;
  --surface: #1e293b;
  --border: #334155;
}

body {
  background: var(--bg);
  color: var(--text);
}

Method 2: Media Query (System Preference)

css
:root {
  --bg: #ffffff;
  --text: #1e293b;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f172a;
    --text: #e2e8f0;
  }
}
Live Preview
Light Mode
Same HTML
Only the CSS variables change — everything just works.
Dark Mode
Same HTML
Only the CSS variables change — everything just works.

7. Using Variables in calc()

Custom properties work seamlessly with calc():

css
:root {
  --sidebar-width: 250px;
  --gap: 1rem;
}

.main-content {
  width: calc(100% - var(--sidebar-width) - var(--gap));
}

Dynamic Spacing Multiplier

css
:root {
  --space-unit: 0.25rem;
}

.padding-4 { padding: calc(var(--space-unit) * 4); }  /* 1rem */
.padding-8 { padding: calc(var(--space-unit) * 8); }  /* 2rem */
.padding-16 { padding: calc(var(--space-unit) * 16); } /* 4rem */

8. Responsive Variables with Media Queries

You can change custom property values at different breakpoints:

css
:root {
  --container-padding: 1rem;
  --heading-size: 1.5rem;
  --grid-columns: 1;
}

@media (min-width: 768px) {
  :root {
    --container-padding: 2rem;
    --heading-size: 2rem;
    --grid-columns: 2;
  }
}

@media (min-width: 1024px) {
  :root {
    --container-padding: 3rem;
    --heading-size: 2.5rem;
    --grid-columns: 3;
  }
}

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

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

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

Pro Tip: This pattern is incredibly powerful — you change values in one place (the media query), and every element that uses those variables adapts automatically.


9. Manipulating Variables with JavaScript

Custom properties can be read and written from JavaScript — making them the bridge between CSS and JS:

Reading a Variable

javascript
const root = document.documentElement;
const primaryColor = getComputedStyle(root).getPropertyValue('--primary-color');
console.log(primaryColor); // "#3b82f6"

Writing a Variable

javascript
document.documentElement.style.setProperty('--primary-color', '#ef4444');
// Every element using --primary-color instantly updates!

Scoped Changes

javascript
const card = document.querySelector('.card');
card.style.setProperty('--card-bg', '#fef3c7');
// Only this card and its children are affected

Use Case: Tracking Mouse Position

css
.card {
  --mouse-x: 50%;
  --mouse-y: 50%;
  background: radial-gradient(
    circle at var(--mouse-x) var(--mouse-y),
    rgba(255, 255, 255, 0.15),
    transparent 50%
  );
}
javascript
card.addEventListener('mousemove', (e) => {
  const rect = card.getBoundingClientRect();
  const x = ((e.clientX - rect.left) / rect.width) * 100;
  const y = ((e.clientY - rect.top) / rect.height) * 100;
  card.style.setProperty('--mouse-x', x + '%');
  card.style.setProperty('--mouse-y', y + '%');
});

10. The @property Rule: Typed Variables

The @property rule (part of CSS Houdini) lets you define the type of a custom property. This enables CSS to understand what the value represents — and crucially, enables transitions and animations on custom properties.

css
@property --gradient-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

.gradient-box {
  background: linear-gradient(
    var(--gradient-angle),
    #6366f1,
    #ec4899
  );
  transition: --gradient-angle 0.5s ease;
}

.gradient-box:hover {
  --gradient-angle: 180deg;
}

Why @property Matters

Without @property, CSS doesn't know that --gradient-angle is an angle, so it can't animate it. With @property, the browser understands the type and can interpolate between values smoothly.

Supported Syntax Types

SyntaxDescriptionExample
<number>Any number42, 3.14
<integer>Whole numbers only1, 100
<length>Length with unit10px, 2rem
<percentage>Percentage value50%
<color>Any color value#ff0000, rgb()
<angle>An angle value45deg, 0.5turn
<length-percentage>Length or percentage10px, 50%
Live Preview
Hover me to rotate the gradient! (Uses @property)

11. Component-Level Variables

Custom properties are perfect for building reusable, configurable components:

css
.btn {
  --btn-bg: #3b82f6;
  --btn-text: #ffffff;
  --btn-padding: 0.5rem 1.25rem;
  --btn-radius: 0.5rem;

  background: var(--btn-bg);
  color: var(--btn-text);
  padding: var(--btn-padding);
  border-radius: var(--btn-radius);
  border: none;
  font-weight: 600;
  cursor: pointer;
}

/* Variant: just override the variables */
.btn-danger {
  --btn-bg: #ef4444;
}

.btn-success {
  --btn-bg: #10b981;
}

.btn-outline {
  --btn-bg: transparent;
  --btn-text: #3b82f6;
  border: 2px solid #3b82f6;
}

.btn-lg {
  --btn-padding: 0.75rem 2rem;
  --btn-radius: 0.75rem;
  font-size: 1.1rem;
}
Live Preview

12. Advanced Patterns

Pattern 1: HSL Color System

Using HSL with custom properties gives you incredible flexibility:

css
:root {
  --hue: 230;
  --sat: 80%;

  --color-primary: hsl(var(--hue), var(--sat), 55%);
  --color-primary-light: hsl(var(--hue), var(--sat), 70%);
  --color-primary-dark: hsl(var(--hue), var(--sat), 40%);
  --color-primary-bg: hsl(var(--hue), var(--sat), 95%);
}

/* Change the ENTIRE color scheme by changing ONE value */
.theme-warm {
  --hue: 15; /* Switches everything to warm orange */
}
Live Preview
--hue: 230 (Blue)
--hue: 150 (Green)
--hue: 25 (Orange)
--hue: 330 (Pink)

Pattern 2: Contextual Spacing

css
:root {
  --density: 1; /* 1 = normal, 0.75 = compact, 1.25 = comfortable */
}

.card {
  padding: calc(1rem * var(--density));
  gap: calc(0.75rem * var(--density));
  font-size: calc(1rem * var(--density));
}

.compact {
  --density: 0.75;
}

.comfortable {
  --density: 1.25;
}

Pattern 3: Stacking with Counters

css
.card-stack {
  --stack-offset: 4px;
}

.card-stack > :nth-child(1) { transform: translateY(0); }
.card-stack > :nth-child(2) { transform: translateY(var(--stack-offset)); }
.card-stack > :nth-child(3) { transform: translateY(calc(var(--stack-offset) * 2)); }

13. Common Mistakes & Gotchas

Mistake 1: Missing Fallback Values

css
/* ❌ No fallback — if --color is undefined, invalid value */
.box { background: var(--color); }

/* ✅ Safe with fallback */
.box { background: var(--color, #3b82f6); }

Mistake 2: Variables Don't Work in Media Queries

css
:root {
  --breakpoint: 768px;
}

/* ❌ This does NOT work — media queries don't support var() */
@media (min-width: var(--breakpoint)) {
  /* ... */
}

Custom properties can be changed inside media queries, but they cannot be used in media query conditions.

Mistake 3: Trying to Animate Default Custom Properties

css
/* ❌ Won't animate — CSS doesn't know the type */
:root { --my-color: #3b82f6; }
.box {
  background: var(--my-color);
  transition: --my-color 0.3s; /* Ignored! */
}

/* ✅ Use @property to define the type */
@property --my-color {
  syntax: '<color>';
  inherits: false;
  initial-value: #3b82f6;
}

Mistake 4: Using Variables for Property Names

css
/* ❌ Variables can store values, not property names */
:root { --prop: background; }
.box { var(--prop): red; } /* Syntax error */

Conclusion

CSS Custom Properties are the foundation of modern CSS architecture:

  • --name: value — Define reusable, dynamic values
  • var(--name, fallback) — Use variables with safe fallbacks
  • Scoped overriding — Redefine variables for specific sections
  • Dark mode — Swap entire themes by toggling variable sets
  • calc() integration — Dynamic calculations with variables
  • JavaScript bridge — Read and write variables from JS
  • @property — Type-safe variables that can be animated
  • Component APIs — Build configurable, variant-driven components

Custom properties replace the need for CSS preprocessor variables in most use cases, and they offer capabilities that preprocessors simply cannot match — like runtime changes, inheritance, and animation.

🧠 Test Your Knowledge

Now that you've learned the concepts, let's see if you can apply them! Take this quick quiz to test your understanding.

PreviousCSS Grid Mastery: From Basics to Advanced LayoutsNextCSS Animations & Transitions Mastery: From Hover Effects to Keyframe Sequences

Written by

Shaik Munsif

Read more articles

Found this helpful? Share it with your network!

On this page

0/35
Question 1 of 10Easy
Score: 0/0

How do you declare a CSS custom property?