CSS - tutorial - 24 -cascade layers

Revision:


Content

"CSS cascade layers" is a CSS feature that allows to define explicit contained layers of specificity an example to clarify "CSS cascade layers" establishing a layer order syntax use cases


"CSS cascade layers" is a CSS feature that allows to define explicit contained layers of specificity

top

These allow to have full control over which styles take priority in a project without relying on "specificity hacks" or "!important".

Managing cascade conflicts and selector specificity has often been considered one of the harder aspects of CSS.

Cascade layers give more direct control over the cascade so that cascading systems can be built more intentionally without relying as much on heuristic assumptions that are tied to selection.

Using the @layer at-rule and layered @imports, our own layers of the cascade can be established — building from low-priority styles like resets and defaults, through themes, frameworks, and design systems, up to highest-priority styles, like components, utilities, and overrides. Specificity is still applied to conflicts within each layer, but conflicts between layers are always resolved by using the higher-priority layer styles.


an example to clarify "CSS cascade layers"

top
code:
        @layer framework {
            .overly#powerful .framework.widget {
              color: maroon;
            }
          }
          
          @layer site {
            .my-single_class {
              color: rebeccapurple;
            }
          }
    

The layers in the example are ordered and grouped so that they don't escalate in the same way that "specificity" and "importance" can.

Cascade layers aren't cumulative like selectors. Adding more layers doesn't make something more important.
They're also not binary like importance — suddenly jumping to the top of a stack — and they are not numbered like z-index, where we have to guess a big number (z-index: 9999999?).
In fact, by default, layered styles are less important than un-layered styles.


establishing a layer order

top

Any number of layers can be created and named or grouped in various ways. But the most important thing to do is to make sure our layers are applied in the right order of priority.

A single layer can be used multiple times throughout the codebase.

Cascade layers stack in the order they first appear : the first layer encountered sits at the bottom (least powerful) and the last layer at the top (most powerful).

But then, above that, un-layered styles have the highest priority.

example: cascade layers
code:
          @layer layer-1 { a { color: red; } }
          @layer layer-2 { a { color: orange; } }
          @layer layer-3 { a { color: yellow; } }
          /* un-layered */ a { color: green; }
      

order: 1/ un-layered styles (most powerful); 2/ layer-3; 3/ layer-2; 4/layer-1 (least powerful)

However, any important styles are applied in a reverse order.

example: cascade layers
code:
          @layer layer-1 { a { color: red !important; } }
          @layer layer-2 { a { color: orange !important; } }
          @layer layer-3 { a { color: yellow !important; } }
          /* un-layered */ a { color: green !important; }
      

order: 1/ !important layer-1 (most powerful); 2/ !important layer-2; 3/ !important layer-3; 4/ !important un-layered styles

Layers can also be grouped, allowing us to do more complicated sorting of top-level and nested layers.

example: cascade layers
code:
            @layer layer-1 { a { color: red; } }
            @layer layer-2 { a { color: orange; } }
            @layer layer-3 {
            @layer sub-layer-1 { a { color: yellow; } }
            @layer sub-layer-2 { a { color: green; } }
            /* un-nested */ a { color: blue; }
            }
            /* un-layered */ a { color: indigo; }
        

order: 1/ un-layered styles (most powerful); 2/ layer-3 un-nested; 3/ layer-3 sub-layer-2; 4/ layer-3 sub-layer-1; 5/ layer-2; 6/ layer-1 (least powerful)

Layers don't have to be defined once in a single location. They can be named so that layers can be defined in one place (to establish layer order), and then we can append styles to them from anywhere.

We can even define a whole ordered list of layers in a single declaration.


syntax

top

Order-setting @layer statements

Since layers are stacked in the order they are defined, it's important that we have a tool for establishing that order all in one place! We can use @layer statements to do that.

The syntax is: @layer <layer-name>#;

The hash (#) means that as many layer names can be added as we want in a comma-separated list: @layer reset, defaults, framework, components, utilities;.

The layer order will be: 1/ un-layered styles (most powerful), 2/ utilities, 3/ components, 4/ framework, 5/ defaults, 6/ reset (least powerful).

This can be done as many times as we want, but what matters is the order each name "first" appears: layers are stacked based on the order in which the layers first appear in your code.

These layer-ordering statements are allowed at the top of a stylesheet, before the "@import rule" (but not between imports). It is highly recommended to use this feature to establish all your layers up-front in a single place.

Block @layer rules

The block version of the @layer rule only takes a single layer name, but then allows you to add styles to that layer:

      @layer <layer-name> {
        /* styles added to the layer */
      }
    

Most things can be put inside an @layer block: media queries, selectors and styles, support queries, etc. Things that cannot be put inside a layer block are things like charset, imports, and namespaces. There is, however, a syntax for importing styles into a layer.

If the layer name hasn't been established before, this layer rule will add it to the layer order. But if the name has been established, this allows to add styles to existing layers from anywhere in the document — without changing the priority of each layer.

If the layer-order has been established up-front with the layer statement rule, there is no need to worry about the order of these layer blocks.

example: cascade layers - syntax
        /* establish the order up-front */
        @layer defaults, components, utilities;

        /* add styles to layers in any order */
        @layer utilities {
          [hidden] { display: none; }
        }

        /* utilities will override defaults, based on established order */
        @layer defaults {
          * { box-sizing: border-box; }
          img { display: block; }
        }
      

Grouping (nested) layers

Layers can be grouped, by nesting layer rules:

        @layer one {
          /* sorting the sub-layers */
          @layer two, three;
        
          /* styles ... */
          @layer three { /* styles ... */ }
          @layer two { /* styles ... */ }
        }
      

This generates grouped layers that can be represented by "joining" the parent and child names with a period. That means the resulting sub-layers can also be accessed directly from outside the group.

        /* sorting nested layers directly */
        @layer one.two, one.three;

        /* adding to nested layers directly */
        @layer one.three { /* ... */ }
        @layer one.two { /* ... */ }
      

The rules of layer-ordering apply at each level of nesting. Any styles that are not further nested are considered “un-layered” in that context, and have priority over further nested styles.

Grouped layers are also contained within their parent, so that the layer order does not intermix across groups.

In this example, the top level layers are sorted first, and then the layers are sorted within each group:

      @layer reset.type, default.type, reset.media, default.media;
    

Layer names are also scoped so that they don't interact or conflict with similarly-named layers outside their nested context. Both groups can have distinct media sub-layers.

This grouping becomes especially important when using @import or <link> to layer entire stylesheets.

Layering entire stylesheets with @import or <link>

Entire stylesheets can be added to a layer using the new layer() function syntax with @import rules:

      /* styles imported into to the <layer-name> layer */
        @import url('example.css') layer(<layer-name>);
    

Anonymous (un-named) layers

It's possible to create anonymous (un-named) layers using the block layer rule:

      @layer { /* ... */ }
      @layer { /* ... */ }
    

Or using the import syntax, with a layer keyword in place of the layer() function:

      
      /* styles imported into to a new anonymous layer */
      @import url('../example.css') layer;

Each anonymous layer is unique, and added to the layer order where it is encountered. Anonymous layers can't be referenced from other layer rules for sorting or appending more styles.

These should probably be used sparingly, but there might be a few use cases: pojects could ensure that all styles for a given layer are required to be located in a single place; third-party tools could “hide” their internal layering inside anonymous layers so that they don't become part of the tool's public API.

Reverting values to the previous layer

There are several ways to "revert" a style in the cascade to a previous value, defined by a lower priority origin or layer. That includes a number of existing global CSS values, and a "new revert-layer keyword" that will also be global (works on any property).

Context: existing global cascade keywords

CSS has several global keywords which can be used on any property to help roll-back the cascade in various ways.

initial sets a property to the specified value before any styles (including browser defaults) are applied. For example, the "initial value" of display is inline, no matter what element we use it on.
inherit sets the property to apply a value from its parent element. This is the default for inherited properties, but can still be used to remove a previous value.
unset acts as though simply removing all previous values — so that inherited properties once again inherit, while non-inherited properties return to their initial value.
revert only removes values that we've applied in the author origin (i.e. the site styles). This is what we want in most cases, since it allows the browser and user styles to remain intact.

New: the revert-layer keyword

Cascade layers add a new global "revert-layer" keyword. It works the same as revert, but only removes values that we've applied in the current cascade layer. We can use that to roll back the cascade, and use whatever value was defined in the previous layers.

example: cascade layers - revert-layer keyword
            @layer default {
              a { color: maroon; }
            }
            
            @layer theme {
              a { color: var(--brand-primary, purple); }
            
              .no-theme {
                color: revert-layer;
              }
            }
        

Reverting important layers

Things get interesting if we add "!important" to the revert-layer keyword. Because each layer has two distinct "normal" and "important" positions in the cascade, this doesn't simply change the priority of the declaration — it changes what layers are reverted.


use cases

top

Several situations of when cascade layers make a lot of sense include:

Less intrusive resets and defaults: to make low-priority defaults that are easy to override. Layers allow to more simply wrap the entire reset stylesheet, either using the "block @layer rule" or when you import the reset.

Managing a complex CSS architecture: as projects become larger and more complex, it can be useful to define clearer boundaries for naming and organizing CSS code. It's likely helpful to restrict projects to a pre-defined set of top-level layers and then extend that set with nested layers as appropriate.

Using third-party tools and frameworks: integrating third-party CSS with a project is one of the most common places to run into cascade issues. Cascade layers give us a way to slot third-party code into the cascade of any project exactly where we want it to live — no matter how selectors are written internally.

Using layers with existing (un-layered, !important-filled) frameworks: