Cascade layers and WordPress

A brief overview of cascade layers and how they can be incorporated within a WordPress project to keep your CSS as maintainable as possible.

Share

Now that cascade layers are supported by all modern browsers, we’ve been looking at how we might incorporate them into the block world of WordPress.

As of writing this, there are only a couple of outliers where cascade layers are not supported.

What are cascade layers

The @layer CSS rule defines a cascade layer, which can then be used to determine the layer order. You can define a set of CSS rules or even a CSS file import as part of a cascade layer.

Once this is set up, a layer that appears after another can override all the styles from the layer before it, regardless of the specificity of the selectors you’ve used within those styles.

This means on a project we can set up our own cascade order ranging through resets, design system, components, utilities and overrides.

CCS Tricks have written up a great and more thorough guide of cascade layers which you can find here.

How might this look in a WordPress project

In terms of CSS, a simple WordPress project may include a base SCSS file that looks like the following:

// Variables
@import "utils/variables/defaults";
...

// Resets
@import "resets/reset";
...

// Design System Base
@import "base/base";
...

// Blocks
@import "blocks/form"
...

// Utils
@import "utils/main";
...

// Overides
@import "blocks/form/overides";
...

You can see that in this instance, the order of the styles @import‘s pretty much match up with the order we would want our styles to cascade in, group by group. Although, this isn’t how they will be treated when loaded in the browser.

If a style selector is more specific inside of the reset or base style import, it’s going to take precedence over styles set on a block level.

With cascade layers, we may choose to set up our base SCSS file like the following instead:

// Layer order
@layer resets, base, blocks, utils, overrides;

// Variables
@import "utils/variables/defaults";
...

// Resets
@import "resets/reset" layer(resets);
...

// Design System Base
@import "base/base" layer(base);
...

// Blocks
@import "blocks/form" layer(blocks);
...

// Utils
@import "utils/main" layer(utils);
...

// Overides
@import "blocks/form/overides" layer(overides);
...

This sets up our cascading order as:

// Layer order
@layer resets, base, blocks, utils, overrides;

This will be the order in which the styles cascade, regardless of the selector specificity.

A simple use case

In a project I may want to use a utility class that sets the border-color of an element to --color-theme-error (a shade of red in this example) with the class .util--error when there is an “error”.

This would mean if a field in a form has an error we could just apply the .util--error class to it. We would want this utility class to be used in any block where we want to highlight an error on a variety of different element types.

In this example let’s say we want to use this utility class on a button in a form. Let’s see how the styles of that form might be made up in the project.

In our reset styles (layer: resets) we might choose to reset all button elements to have a black border with:

button {
  border-color: #000;
}

Next, our design system might dictate that our base border colour of buttons should be the primary theme colour of the design system. So our base styles (layer: base) would include the following:

button {
  border-color: var(--color-theme-primary);
}

Then we might have a form block which we have decided the button doesn’t use the primary theme colour but the secondary. We would set that inside our block styles (layer: blocks):

.wp-block-form button {
  border-color: var(--color-theme-secondary);
}

All fine so far.

Now we come to our utility class. We create that class in our utility styles (layer: utility) and that sets the border of all elements to be the themes error colour like so:

.util--error {
  border-color: var(--color-theme-error);
}

Without cascade layers this wouldn’t change the buttons in the forms block (.wp-block-form) and you would need additional css to specify .wp-block-form .util--error or be more specific on the utility class about which elements you want it to work with.

But with our cascade layers and layer order this isn’t an issue.

// Layer order
@layer resets, base, blocks, utils, overrides;

Because the utils come after the blocks in the cascade order, it doesn’t matter about the specificity of the selector and the util class will take dominance.

You can see how this works without and then with cascade layers.

Without cascading layers

See the Pen Without Cascade Layers by Shaun Buckle (@shaunbb) on CodePen.

With cascading layers

See the Pen With Cascade Layers by Shaun Buckle (@shaunbb) on CodePen.

Summary

Cascade layers fit nicely in a WordPress project and allows us to continue to write block specific styles while still allowing us to target components in those blocks, without having to write ultra specific selectors.

Integrating cascade layers early into a project will be much easier, as retrofitting it into an existing project is bound to cause all sorts of issues with your styles.

With the way we’ve seen our WordPress sites grow over time, keeping our CSS (as well as the rest of the code base) as maintainable as possible in the long term is key.

We are hoping cascade layers will be another tool that will help us do that and hopefully avoid the overuse of the sometimes dreaded !important rule.


Interested in learning more about enterprise WordPress?
Get in touch with the team today