This article describes how to apply cascade layers in WordPress, in order to control how CSS rules apply to HTML elements.
How WordPress Handles Styling
WordPress uses themes to customize the appearance of a website. Every theme has its own design, including colors, fonts, effects, animations and numerous other styles.
Themes use CSS rules to impose their styling to the website’s HTML elements.
Besides themes, plugins that render HTML elements at the front-end, like forms, may use their own CSS to style them.
The basic concept in WordPress is that the theme’s CSS rules need to prevail over any other CSS rules, so that the appearance of the website is the same everywhere.
However, there are cases where HTML elements, rendered by a plugin, need to have a very specific styling and the theme’s CSS spoils it.
There are several ways to resolve this problem; make the plugin’s CSS rules more specific, put the elements inside a shadow DOM, or define cascade layers.
Here we discuss about the last one, cascade layers.
Cascade Layers
Cascade layers is a feature of CSS3 and can be used to separate CSS rules into layers. Layers are applied in a sequence. The first layer defined is applied first, the second is applied next and so on. CSS rules belonging to a next layer prevail over CSS rules of a previous layer, regardless of the CSS selectors. It is an efficient way to control styling, when having competing CSS rules coming 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.
So the following paragraphs describe how to put all stylesheets and styles loaded by WordPress inside layers and define their order.
How to Do it
WordPress loads the CSS files of the theme and of any plugins at the front-end using wp_enqueue_style()
function. This function adds <link>
tags inside the HTML code of the page, or post, to load the files.
Furthermore, the theme or plugins may enqueue additional styles using add_inline_style()
function. This function adds <style>
tags inside the HTML code.
Unfortunately, <link>
tag does not provide any attribute to set the cascade layer of the loaded CSS file (maybe in the future). CSS files loaded with <link>
tags are unlayered.
The only way to load a CSS file inside a layer is by using @import
rule inside a <style>
tag. So we need to replace the <link>
tags that WordPress loads with <style>
tags using @import
rule.
Here is sample PHP code 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 'normal-styles' layer.
// We also define another layer, 'stronger-styles', which has a higher precendence.
// 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 precendence over any other styles loaded above.
}
The result of the above code is that all styles loaded by the theme and plugins will be put in ‘normal-styles’ layer.
Though this method may reduce loading speed of the page, it is the only one that can apply cascade layers in WordPress for the moment.
For additional information or comments, do not hesitate to contact us.
The Iptanus Team