Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 23

CSS Custom Properties: Purpose, Scoping, and Advantages

Recommended resources

Hello! Welcome to the final lesson in our module on advanced CSS topics.

In our previous session, we explored the mobile-first design philosophy, understanding it as a strategy for creating clean, performant, and maintainable responsive layouts using min-width media queries. We also discussed a pragmatic approach, the "path of least resistance," for styling individual components.

Today, we'll dive into another feature that fundamentally changes how we write and structure CSS: custom properties, often referred to as CSS variables. Their power goes far beyond simply storing a value; they introduce a dynamic, cascade-aware system for managing styles that preprocessors like Sass can't replicate.

By the end of this lesson, you will be able to describe the purpose of CSS custom properties (variables), their scoping rules, and their advantages over preprocessor variables. Given your background in software development, you'll find the concepts of dynamic scoping and inheritance here both familiar and uniquely applied within the context of CSS.

What are Custom Properties, and How Do They Differ from Preprocessor Variables?

At a glance, a CSS custom property might look similar to a variable in Sass or Less. However, their underlying mechanics are completely different.

A preprocessor variable (e.g., $my-color: #f00; in Sass) is a static construct. During compilation, the preprocessor finds every instance of $my-color and replaces it with the value #f00. The final, browser-ready CSS file contains no variables, only the hardcoded values.

A CSS custom property (e.g., --my-color: #f00;) is a live CSS property that the browser understands. It is not compiled away. It exists within the DOM, participates in the cascade, is inheritable, and can be manipulated in real-time with CSS or JavaScript.

To begin, let's explore this fundamental difference.

A Strategy Guide To CSS Custom Properties

The article 'A Strategy Guide To CSS Custom Properties' from Smashing Magazine provides an excellent starting point. It covers the syntax and immediately contrasts custom properties with their preprocessor counterparts.

Please read the first two main sections: 'How Are They Similar To Variables In Preprocessors?': Focus on the syntax differences and the key point that custom properties are actual CSS properties tied to selectors. 'Dynamic Vs. Static': This is the most crucial part. Pay close attention to how it explains the dynamic nature of custom properties, which are subject to inheritance and the cascade, unlike the static nature of preprocessor variables.

The key takeaway is that preprocessor variables are a developer convenience during compilation, while custom properties are a dynamic feature of the browser's rendering engine.

This image provides a great side-by-side summary of the differences:

CSS Variables vs. SCSS Variables Comparison
This table compares the features of native CSS custom properties (left) against SCSS variables (right). It highlights that CSS variables are live in the browser, can be changed dynamically, and are scoped to elements, whereas SCSS variables are static, compiled away, and exist only before the CSS is generated.

Scoping, Inheritance, and the Cascade

The real power of custom properties comes from how they interact with the core principles of CSS: scope, inheritance, and the cascade.

  • Scoping: You can define a custom property's scope by the selector you attach it to.

    • Global Scope: Defining a property on the :root pseudo-class makes it available globally, as :root matches the <html> element, the highest-level element in the DOM. This is the most common place to define a design system's tokens (colors, fonts, spacing).
    • Local Scope: Defining a property on a specific class (e.g., .my-component) limits its scope to that element and its descendants.
  • Inheritance: Custom properties are inherited by default. If you set --main-color: blue on a <div>, all of its children can access var(--main-color) and it will resolve to blue, unless it's overridden.

  • The Cascade: This is where custom properties truly shine. You can redefine the value of a custom property within a more specific selector, a media query, or a pseudo-class like :hover. The new value will cascade down and affect any descendant elements using that variable.

This is the primary advantage over preprocessor variables. With Sass, you can't change a variable's value inside a media query and expect it to work; you have to rewrite the entire rule. With custom properties, you just change the variable's value, and the change propagates automatically.

CSS Variables - CSS vs Sass - variables inside media queries

Kevin Powell provides a perfect demonstration of this advantage, showing how custom properties make responsive styling significantly more concise and maintainable.

Watch from 0:25 to 5:11. He first shows how Sass variables are static and exist only pre-compilation (0:25). He then demonstrates the verbose code required to change font sizes at different breakpoints using Sass (1:29). Finally, he contrasts this with the elegant and DRY (Don't Repeat Yourself) approach using CSS custom properties, where only the variable values are redefined within the media query (2:37).

This dynamic, cascading behavior is why the official name is "CSS Custom Properties for Cascading Variables." The term "CSS Variables" is a common alias, but understanding that they are fundamentally properties that cascade is key to using them effectively.

Is there a difference between CSS custom properties and CSS variables?

A highly-rated answer on Stack Overflow provides further clarification on this terminology, which is helpful for someone with a programming background who might expect variables to behave differently.

Skim through the answers to grasp the consensus. Focus on: The explanation that custom properties are not 'global' in a traditional programming sense; their values are scoped and cascade. The point that the official specification name emphasizes their 'cascading' nature. The final code example, which clearly illustrates that a property defined in one scope (#foo) is not accessible to a sibling, reinforcing the rules of CSS inheritance.


Advanced Strategies: Component-Level State

While global theming (e.g., light/dark mode) is a classic use case for custom properties, a more advanced and powerful technique is to use them to manage the internal state of a component. By defining locally-scoped variables, you can create highly reusable and configurable components without a proliferation of modifier classes.

The strategy is to define a set of "private" custom properties on a component's base class. These properties can then be easily overridden on a parent element to change the appearance of child elements within the component, such as icons, backgrounds, or hover effects.

Using CSS custom properties like this is a waste

Kevin Powell again provides an excellent deep dive into this exact strategy, showing how to move beyond simple color variables to creating robust, self-contained components.

Watch from 1:39 to 5:58. Pay close attention to these concepts: Locally Scoped Custom Properties: He defines variables directly on the .plan component class. 'Pseudo-private' Naming: He uses an underscore prefix (e.g., _shadow) as a convention to indicate a property is meant for internal component use. The Pattern: He sets a CSS property (e.g., fill) to use a public variable (var(--icon)), which in turn has a fallback to a private variable (var(--_icon)). This allows a default to be set within the component while still allowing easy overrides from the outside. The result is that he can change multiple styles within the component (backgrounds, icon fills) just by changing one or two variables on a single parent selector, dramatically reducing CSS complexity.

This pattern of creating a "public API" for a component using custom properties is a sophisticated technique that aligns well with the principles of component-based architecture you're familiar with from your work with React.


Conclusion

In this lesson, we unpacked the power of CSS custom properties. They are far more than a simple replacement for preprocessor variables; they are a dynamic, browser-native feature that fully integrates with the CSS cascade.

Key Takeaways:

  • Dynamic vs. Static: CSS custom properties are dynamic, "live" properties understood by the browser, whereas preprocessor variables are static and compiled away before the browser sees the CSS.
  • Scoping and Inheritance: Custom properties follow standard CSS scoping rules. They can be defined globally (:root) or locally, and they are inherited by descendants.
  • The Power of the Cascade: Their main advantage is the ability to be redefined within media queries, pseudo-classes (:hover, :focus), or more specific selectors. This allows for creating responsive and interactive styles in a very clean and maintainable way.
  • Strategic Use: They are ideal for global theming (design tokens) and for creating encapsulated, configurable components by defining a local "API" of variables.

This completes our module on Advanced CSS. You now have a solid conceptual foundation in modern layout with Flexbox and Grid, performance-conscious animations, responsive design strategies, and now, scalable styling with custom properties.


What's Next?

We are now making a significant shift from styling and presentation to the core logic of the web. In the next module, we will begin our deep dive into JavaScript. Our first lesson will start at the most fundamental level: "Explain how primitive and reference types differ in memory storage and mutation behavior." This is a crucial concept that underpins much of JavaScript's behavior, and your computer science background will be a great asset in understanding these memory-level distinctions.