This article explains how to apply cascade layers in WordPress to control how CSS rules apply to HTML elements.
How WordPress handles styling
WordPress uses themes to control the appearance of a site. Every theme has its own design — colours, fonts, effects, animations and much else besides.
Themes impose that styling on the site’s HTML elements through CSS rules.
Plugins that render elements on the front end — forms, for instance — may bring CSS of their own.
The principle in WordPress is that the theme’s rules should prevail over all others, so that the site looks consistent throughout.
There are cases, though, where elements rendered by a plugin need very specific styling, and the theme’s CSS spoils it.
There are several ways round this: make the plugin’s rules more specific, put the elements inside a shadow DOM, or define cascade layers.
This article covers the last of those.
Cascade layers
Cascade layers are a CSS feature for separating rules into ordered layers. The first layer defined is applied first, then the second, and so on, and a rule in a later layer beats a rule in an earlier one whatever the selectors. It is an efficient way to control styling when competing rules arrive from many stylesheets.
WordPress does not support cascade layers. All stylesheets and styles loaded in a page or post are unlayered. According to cascade layer rules, unlayered styles have a higher precedence over layered styles within the same origin. So, if we want to make some CSS rules stronger, we can’t just put them in a layer. We need to put all loaded stylesheets and styles in layers and define their order.
The rest of this article describes how to put every stylesheet and style WordPress loads into layers, and how to set their order.
How to do it
WordPress loads the CSS files of the theme and of any plugins on the front end with the wp_enqueue_style() function, which adds <link> tags to the page’s HTML.
The theme or plugins may also enqueue extra styles with the add_inline_style() function, which adds <style> tags.
The <link> tag has no attribute for setting the cascade layer of the file it loads, so CSS brought in that way is always unlayered.
The only way to load a file into a layer is an @import rule inside a <style> tag. So the <link> tags WordPress produces have to be replaced with <style> tags that use @import.
Here is PHP that does it.
// We use 'wp_enqueue_scripts' action with a very high priority so that it runs
// last. This way we make sure that all plugins and the theme have enqueued
// their styles and they are available in global $wp_styles object.
add_action( 'wp_enqueue_scripts', 'enqueue_layered_scripts', 9999999999 );
function enqueue_layered_scripts() {
global $wp_styles;
// Get all styles that will be enqueued by WordPress
$styles = $wp_styles->registered;
// Define the layers and their order.
// We assign all styles loaded by WordPress to the 'normal-styles' layer.
// We also define another layer, 'stronger-styles', which has a higher precedence.
// We can put any styles we want in there.
$layers = '@layer normal-styles, stronger-styles;';
// Iterate through the styles in order to change the way they are enqueued
foreach ( $styles as $key => $style ) {
// Check if the style loads a css file
$src_exists = is_string($styles[$key]->src);
// Prepare the CSS code that will be loaded in place of the <link> tag.
// First, we define the layers and their order. Unfortunately we cannot
// be sure which style in the list will be loaded first. So we need to
// put the definition of the layers in every CSS code.
$code = $layers.' ';
// Then we add an @import rule to load the CSS file, if exists. We set
// the layer of the imported file to be 'normal-styles'.
if ( $src_exists ) $code .= '@import url("'.$style->src.'") layer(normal-styles); ';
// The style may contain extra CSS code that has been added through
// add_inline_style() function. We also need to put this code inside
// 'normal-styles' layer. We do this by enclosing the code inside a
// @layer rule. We prepend '@layer normal-styles {' inside the extra CSS
// code.
// Notice that we do not close the opening @layer block. We do this on
// purpose because themes or plugins may add more CSS code after this
// function and we want to have that too inside the layer. It is ok that
// we do not close the @layer block, the browser will do it
// automatically.
$code .= '@layer normal-styles { ';
$after = $wp_styles->get_data( $style->handle, 'after' );
if ( ! $after ) $after = array();
// We prepend the prepared CSS code to the extra CSS code of the style.
array_unshift($after , $code);
$wp_styles->add_data( $style->handle, 'after', $after );
// We empty the styles 'src' property so that the style is not loaded
// with a <link> tag.
if ( $src_exists ) $styles[$key]->src = "";
}
// We can put more CSS code here that is loaded in 'stronger-styles' and has
// higher precedence over any other styles loaded above.
}
The result of the above code is that every style loaded by the theme and by plugins ends up in the ‘normal-styles’ layer.
The method costs a little page-load speed, but for now it is the only way to apply cascade layers in WordPress.
If you have questions or comments, please contact us.
The Iptanus team

Ask a question
Answered by Iptanus, usually within a working day.