search
HomeWeb Front-endCSS TutorialUsing Custom Property 'Stacks' to Tame the Cascade

CSS Custom Properties: New Ways to Control Cascading and Inheritance

Using Custom Property

Since the birth of CSS in 1994, cascading and inheritance have defined how we design on web pages. Both are powerful features, but as developers, we have very limited control over how they interact. Selector specificity and source code order provide some minimal "hierarchical" control, but lacks nuances - and inheritance requires an unbroken lineage . Now, CSS custom properties allow us to manage and control cascading and inheritance in new ways.

This article will show how to use a custom property "stack" to solve some common problems in cascading: from scoped component styles to more explicit intent hierarchy.

Quickly learn about custom properties

The browser uses a vendor prefix (such as -webkit- or -moz-) to define new properties in the same way, and we can use the "empty" -- prefix to define our own custom properties. Like variables in Sass or JavaScript, we can use them to name, store, and retrieve values—but like other properties in CSS, they are cascading and inherited with the DOM.

 <code>/* 定义自定义属性*/ html { --brand-color: rebeccapurple; }</code>

To access these captured values, we use the var() function. It has two parts: first the name of the custom property, and then the fallback value when the property is not defined:

 <code>button { /* 如果可用,则使用--brand-color,否则回退到deeppink */ background: var(--brand-color, deeppink); }</code>

This is not a support fallback from older browsers. If the browser does not understand the custom properties, it ignores the entire var() declaration. Instead, this is a built-in way to deal with undefined variables, similar to the font stack that defines a fallback font family when the font is not available. If we do not provide fallback values, the default is not set.

Build variable "stack"

This ability to define fallback values ​​is similar to the "font stack" used in the font-family property. If the first series is not available, the second series will be used, and so on. The var() function only accepts a single fallback value, but we can nest the var() function to create a custom attribute fallback "stack" of any size:

 <code>button { /* 尝试Consolas,然后是Menlo,然后是Monaco,最后是monospace */ font-family: Consolas, Menlo, Monaco, monospace; /* 尝试--state,然后是--button-color,然后是--brand-color,最后是deeppink */ background: var(--state, var(--button-color, var(--brand-color, deeppink))); }</code>

If the nested syntax for stacking properties looks bulky, you can use a preprocessor such as Sass to make it more compact.

A single fallback value limit is required to support fallback values ​​containing commas—such as font stacks or hierarchical background images:

 <code>html { /* 后备值为"Helvetica, Arial, sans-serif" */ font-family: var(--my-font, Helvetica, Arial, sans-serif); }</code>

Define "scope"

The CSS selector allows us to dive into the HTML DOM tree and style elements anywhere on the page or elements in a specific nested context.

 <code>/* 所有链接*/ a { color: slateblue; } /* section 内的链接*/ section a { color: rebeccapurple; } /* article 内的链接*/ article a { color: deeppink; }</code>

This is useful, but it doesn't capture the reality of "modular" object-oriented or component-driven styles. We may have many articles and asides nested in various configurations. We need a way to clarify which context or scope should take precedence when they overlap.

Proximity scope

Suppose we have a .light theme and a .dark theme. We can use these classes on the root element to define the page-wide default values, but we can also apply them to specific components nested in various ways:

Every time we apply one of the color pattern classes, the background and color properties are reset and then inherited by the nested titles and paragraphs. In our main context, colors inherit from the .light class, while nested titles and paragraphs inherit from the .dark class. Inheritance is based on direct bloodline, so the nearest ancestor with defined values ​​will take precedence. We call it proximity .

Proximity is important for inheritance, but it has no effect on selectors, which rely on specificity. This becomes a problem if we want to style something in the inner dark or light container.

Here I try to define light and dark button variants. The light mode buttons should be rebeccapurple with white text so that they stand out, while the dark mode buttons should be plum with black text. We directly select the buttons based on the light and dark contexts, but this doesn't work:

Some buttons are in both contexts, with .light and .dark ancestors. In this case, what we want is to have the recent topic take over (inheriting proximity behavior ), but what we get is the second selector overriding the first selector (cascade behavior). Since these two selectors have the same specificity, the order of the source code determines the winner.

Custom properties and proximity

We need a way to inherit these properties from the topic, but only apply them to specific child elements, such as our buttons. Custom properties make this possible! We can define values ​​on light and dark containers, but only use their inherited values ​​on nested elements such as our buttons.

We will first set the button to use custom properties and use fallback "default" values ​​in case those properties are undefined:

 <code>button { background: var(--btn-color, rebeccapurple); color: var(--btn-contrast, white); }</code>

Now we can set these values ​​based on the context, they will scope to the corresponding ancestor according to proximity and inheritance:

 <code>.dark { --btn-color: plum; --btn-contrast: black; } .light { --btn-color: rebeccapurple; --btn-contrast: white; }</code>

As an additional bonus, we use less code overall, as well as a unified button definition:

I think this is an API to create available parameters for the button component. Both Sara Soueidan and Lea Verou have covered this very well in recent articles.

Component ownership

Sometimes proximity is not sufficient to define scope. When JavaScript frameworks generate "scope styles", they are establishing ownership of specific object elements. The Tab Layout component has the tab itself, but does not have the content behind each tab. This is also what the BEM convention tries to capture in complex .block__element class names.

Nicole Sullivan coined the term “donut scope” in 2011 to discuss this issue. Although I believe she has updated ideas about this issue, the fundamental problem has not changed. Selectors and specificity are great for describing how we build detailed styles on top of a broad pattern, but they do not convey a clear sense of ownership.

We can use a custom property stack to help solve this problem. We will first create "global" properties on the element, which are used for our default colors:

 <code>html { --background--global: white; --color--global: black; --btn-color--global: rebeccapurple; --btn-contrast--global: white; }</code>

Now we can use it anywhere we want to reference it. We will use the data-theme property to apply our foreground and background colors. We want the global value to provide the default fallback value, but we also want the option to overwrite it with a specific topic. This is where the "stack" comes in:

 <code>[data-theme] { /* 如果没有组件值,则使用全局值*/ background: var(--background--component, var(--background--global)); color: var(--color--component, var(--color--global)); }</code>

Now we can define the reverse component by setting the *--component property to the reverse of the global property:

 <code>[data-theme='invert'] { --background--component: var(--color--global); --color--component: var(--background--global); }</code>

But we don't want these settings to inherit from the ownership donut, so we reset these values ​​to initial (undefined) on each topic. We need to do this in a lower specificity or earlier source code order so that it provides default values ​​that each topic can override:

 <code>[data-theme] { --background--component: initial; --color--component: initial; }</code>

The initial keyword has a special meaning when used for custom properties, reverting it to the Guaranteed-Invalid state. This means it will not be passed to set background: initial or color: initial, the custom property will become undefined, and we will fall back to the next value in the stack, i.e. the global settings.

We can do the same with the button and then make sure that data-theme is applied to each component. If no specific topic is given, each component will default to the global topic:

Define "source"

A CSS cascade is a series of filtering layers used to determine which value should be preferred when defining multiple values ​​on the same property. We most often interact with specific layers, or final hierarchy based on source code sequence—but the first layer of the cascade is the "source" of the style. Source describes the source of the style—usually the browser (default), user (preference), or author (us).

By default, the author style overrides user preferences and user preferences override browser defaults. This changes when anyone applies !important to a style, and the source is reversed: the browser !important style has the highest source, then the important user preferences, then our author important styles, above all normal layers. There are some other sources, but we won't go into details here.

When we create a custom property "stack", we are building a very similar behavior. If we want to represent an existing source as a custom property stack, it will look like this:

 <code>.origins-as-custom-properties { color: var(--browser-important, var(--user-important, var(--author-important, var(--author, var(--user, var(--browser)))))); }</code>

These layers already exist, so there is no reason to recreate them. But when we layer our "global" and "component" styles above, we are doing something similar - creating a "component" source layer that overwrites our "global" layer. The same method can be used to solve various hierarchical problems in CSS, which cannot always be described with specificity:

  • Override » Components » Theme » Default Values
  • Topic » Design a system or framework
  • Status » Type » Default Value

Let's take a look at some more buttons. We need a default button style, a disabled state and various button "types" such as danger, primary, and secondary. We want the disabled state to always override the type variant, but the selector cannot capture this difference:

But we can define a stack that provides the "type" and "state" layers in the order we want to prioritize:

 <code>button { background: var(--btn-state, var(--btn-type, var(--btn-default))); }</code>

Now, when we set two variables, the state will always take precedence:

I've created a cascading color framework using this technique that allows custom theme settings based on layering:

  • Predefined topic properties in HTML
  • User color preferences
  • Light and dark modes
  • Global theme defaults

Mix and match

These methods can be at the extreme, but most daily use cases can be processed using two or three values ​​in the stack, usually using the above techniques:

  • Define the variable stack of the layer
  • Inheritance sets them based on proximity and scope
  • Apply initial values ​​carefully to remove nested elements from scope

We have been using these custom properties "stack" in our OddBird projects. We are still exploring, but they have helped us solve problems that are difficult to solve with just selectors and specificity. With custom properties, we don't have to fight cascade or inheritance. We can capture and utilize them as expected and have better control over how they are applied in each instance. For me, this is a huge win for CSS – especially when developing style frameworks, tools and systems .

The above is the detailed content of Using Custom Property 'Stacks' to Tame the Cascade. For more information, please follow other related articles on the PHP Chinese website!

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Adding Box Shadows to WordPress Blocks and ElementsAdding Box Shadows to WordPress Blocks and ElementsMar 09, 2025 pm 12:53 PM

The CSS box-shadow and outline properties gained theme.json support in WordPress 6.1. Let's look at a few examples of how it works in real themes, and what options we have to apply these styles to WordPress blocks and elements.

Working With GraphQL CachingWorking With GraphQL CachingMar 19, 2025 am 09:36 AM

If you’ve recently started working with GraphQL, or reviewed its pros and cons, you’ve no doubt heard things like “GraphQL doesn’t support caching” or

Making Your First Custom Svelte TransitionMaking Your First Custom Svelte TransitionMar 15, 2025 am 11:08 AM

The Svelte transition API provides a way to animate components when they enter or leave the document, including custom Svelte transitions.

Classy and Cool Custom CSS Scrollbars: A ShowcaseClassy and Cool Custom CSS Scrollbars: A ShowcaseMar 10, 2025 am 11:37 AM

In this article we will be diving into the world of scrollbars. I know, it doesn’t sound too glamorous, but trust me, a well-designed page goes hand-in-hand

Show, Don't TellShow, Don't TellMar 16, 2025 am 11:49 AM

How much time do you spend designing the content presentation for your websites? When you write a new blog post or create a new page, are you thinking about

Building an Ethereum app using Redwood.js and FaunaBuilding an Ethereum app using Redwood.js and FaunaMar 28, 2025 am 09:18 AM

With the recent climb of Bitcoin’s price over 20k $USD, and to it recently breaking 30k, I thought it’s worth taking a deep dive back into creating Ethereum

What the Heck Are npm Commands?What the Heck Are npm Commands?Mar 15, 2025 am 11:36 AM

npm commands run various tasks for you, either as a one-off or a continuously running process for things like starting a server or compiling code.

Let's use (X, X, X, X) for talking about specificityLet's use (X, X, X, X) for talking about specificityMar 24, 2025 am 10:37 AM

I was just chatting with Eric Meyer the other day and I remembered an Eric Meyer story from my formative years. I wrote a blog post about CSS specificity, and

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
3 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
3 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. How to Fix Audio if You Can't Hear Anyone
3 weeks agoBy尊渡假赌尊渡假赌尊渡假赌

Hot Tools

mPDF

mPDF

mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

SublimeText3 English version

SublimeText3 English version

Recommended: Win version, supports code prompts!

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

Powerful PHP integrated development environment

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment