I have a project using latest Sage (main branch GitHub) using a traditional WP setup (no Bedrock) on DevKinsta.
Problem
I’ve noticed that some rules generated by Tailwind being overridden by rules in global-inline-styles (global block styles). Tailwind’s adoption of cascade layers in V4 seems to be the culprit. Since unlayered styles take precedence over layered styles, styles in global-styles-inline-css
can override styles in app.css
. For example, the following selectors have the same specificity, so without layers whichever is defined last should be applied.
app.css
a {
color: inherit;
text-decoration: inherit;
}
global-styles-inline-css
a:where(:not(.wp-element-button)) {
text-decoration: underline;
}
However, since Tailwind began wrapping it’s styles in cascade layers in V4, the second rule will always win no matter the order of definition. You can also see that .no-underline is overridden as well:
Possible Solutions
-
Important modifier
One solution is to use the important modifier, but this would still be a breaking change for those migrating from the latest release, requiring theme devs to add the modifier to all of the clashing styles. Additionally, rules in Tailwind’s default CSS reset can still be overridden as well. -
Important Flag
A more general solution would be to add the important flag, which marks all utilities as!important
. While this prevents having to modify individual classes on migration, the blanket use of!important
can cause unexpected issues down the line. -
More Layers
My last idea is to wrapglobal-styles
(which is generated by wp_enqueue_global_styles) in a layer that is defined prior to Tailwind’s layers. I’ve written up some preliminary code below.
Code
First, dequeue and de-register global:
app/setup.php
add_action('wp_enqueue_scripts', function () {
wp_dequeue_style('global-styles-inline-css');
wp_deregister_style('global-styles-inline-css');
});
This is a modified version of wp_enqueue_global_styles, which wraps the styles in a cascade layer called “wp-global-styles”
app/setup.php
add_action('wp_enqueue_scripts', function () {
$is_block_theme = wp_is_block_theme();
add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
$stylesheet = wp_get_global_stylesheet();
if ( $is_block_theme ) {
$stylesheet .= wp_get_global_stylesheet( array( 'custom-css' ) );
}
if ( empty( $stylesheet ) ) {
return;
}
wp_register_style( 'global-styles', false );
// wraps styles in in global-block-styles layer
wp_add_inline_style( 'global-styles', "@layer global-block-styles { $stylesheet }");
wp_enqueue_style( 'global-styles' );
wp_add_global_styles_for_blocks();
}, 10, 0);
Lastly, define the global-block-styles layer in app.css
@layer "global-block-styles";
@import "tailwindcss";
...
I wasn’t able to find any filters for the wp_head
action, so this code generates global-styles
twice - once during the wp_head
action when wp_enqueue_global_styles
is called and again in the above custom action. Another possible solution, which I didn’t write up, would be to manually build the head content, to prevent building global-styles
twice.
Anyway, would love to hear your thoughts on this.