Scoping Custom Properties
- CSS Basics
- Selectors & Specificity
- Box Model & Units
- Typography & Text Styling
- Colors & Color Spaces
- Positioning & Stacking Context
- Flexbox
- Grid
- Grid Advanced
- Responsive Design
- Container Queries
- Pseudo-classes & Pseudo-elements
- Modern Selectors
- CSS Custom Properties
- CSS Functions
- Transitions & Animations
- Transforms
- Forms & Input Styling
- CSS Architecture & Methodologies
- Accessibility in CSS
- Performance & Optimization
- View Transitions & Modern APIs
- Cascade Layers & Scope
Custom Properties Follow Normal Cascade Rules
Unlike Sass variables, which are resolved once at compile time, CSS custom properties are live and follow the same cascade and inheritance rules as any other CSS property - meaning where a variable is declared directly controls where it is visible and what value it holds.
Global Scope with :root
:root {
--gap: 16px;
}A variable declared on :root is inherited by every element in the document, making it effectively global.
Local Scope on a Specific Element
.dense-list {
--gap: 4px; /* only visible to .dense-list and its descendants */
}Declaring the same variable name inside a specific selector creates a new value that only applies within that selector's scope - anything outside it still sees the global :root value.
A Practical Pattern: Component-Level Variables
.card {
--card-padding: 16px;
padding: var(--card-padding);
}
.card.compact {
--card-padding: 8px; /* overrides just for this variant */
}Defining a variable scoped to a component, then overriding it on a modifier class, is a common way to build variant styles without duplicating every property.
Why Understanding Scope Matters
A variable that seems to "not work" is very often actually working correctly, but scoped somewhere unexpected - defined inside a sibling element rather than a shared ancestor, for instance, so it never inherits down to where it is being used. Checking where a variable is declared, relative to where it is read, is the first step in debugging this.