Load global CSS in block theme in Wordpress
Update (2023-02-27 - 27 Feb)
Initially, when I wrote the first version of this guide, I wasnt't aware of add_editor_style
wordpress function, that is used to add css to the Tinymce editor (the classic editor) and now its used to add css to the block editor (Gutenberg or Block Editor).
Here is the "update" of this guide:
In frontend use wp_enqueue_style
and add dependency of css generated from theme.json
, so your will have priority if has same css specificity.
In block editor iframe, use add_editor_style
.
/* .../wp-content/my-theme/my-custom-css.css */
.text-2xl {
font-size: 1.5rem !important;
}
@media (max-width: 600px) {
.mobile\:text-2xl {
font-size: 1.5rem !important;
}
}
/**
* Load custom global CSS into both block editor "iframe" and frontend
*
* @return void
*/
function my_theme_enqueue_custom_global_css_in_blocks() {
// Add custom CSS to frontend.
wp_enqueue_style(
'my-custom-css',
get_template_directory_uri() . '/my-custom-css.css'
array( 'global-styles' ),// say to wordpress to inject this file after the css file generated from "theme.json"
);
// Add custom CSS to block editor "iframe".
add_editor_style( 'my-custom-css.css' );
}
add_action( 'after_setup_theme', 'my_theme_enqueue_custom_global_css_in_blocks' );
Your css stylesheet does not need special treatments, it works in both contexts(block editor/site editor and frontend).
In frontend, you just add your stylesheet after the one generated from theme.json
, to be able to overrides without increasing css specificity.
In the block editor (and site editor) "iframe" you use the function add_editor_style, does exactly what this guide does manually, for the iframe part.
The function add_editor_style
injects a CSS file in the block editor "iframe" and adds .editor-styles-wrapper
scoped selector to every css definition, also in media query definitions.
For example, .text-2xl
become .editor-styles-wrapper .text-2xl
Thanks to carolinan that suggested this in this issue.
End of update.
Original Guide (OLD)
Goal of this guide:
Load a CSS file insde both editor and frontend.
This CSS contains classes that when applied, they must override an already present class coming from the generated CSS of theme.json
.
This is really difficult to reason about and also extremely hacky. Is very difficult for me to write it clearly.
I'll go step by step.
Theme.json
This is our theme.json
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 2,
"settings": {
"typography": {
"fontSizes": [
{
"slug": "base",
"name": "base",
"size": "1rem"
},
{
"slug": "lg",
"name": "lg",
"size": "1.125rem"
},
{
"slug": "xl",
"name": "xl",
"size": "1.25rem"
},
{
"slug": "2xl",
"name": "2xl",
"size": "1.5rem"
},
{
"slug": "3xl",
"name": "3xl",
"size": "1.875rem"
},
{
"slug": "4xl",
"name": "4xl",
"size": "2.25rem"
},
{
"slug": "5xl",
"name": "5xl",
"size": "3rem"
},
{
"slug": "6xl",
"name": "6xl",
"size": "3.75rem"
},
{
"slug": "7xl",
"name": "7xl",
"size": "4.5rem"
}
]
}
This theme.json will be converted into CSS (by Wordpress), but the CSS for the block editor is different from the frontend one.
/* Theme.json generated CSS - Editor */
.editor-styles-wrapper .has-6-xl-font-size {
font-size: var(--wp--preset--font-size--6-xl) !important;
}
/* Theme.json generated CSS - Frontend */
.has-6-xl-font-size {
font-size: var(--wp--preset--font-size--6-xl) !important;
}
Environments
Let’s look at block editor
and frontend
environments.
Block Editor
In block editor, the live preview is inside an iframe, the UI of the editor is external to the iframe.
Frontend
In frontend, your content is not wrapped by an iframe.
Start
Let’s say that we change the text font-size like so
...but on mobile font-size is too big
So what we want is to create a CSS file with some media query and apply them
@media (max-width: 600px) {
.mobile\:text-2xl {
font-size: 1.5rem;
}
}
So, even if the CSS is not yet injected we apply a custom CSS class to the paragraph
Now it’s time to inject the CSS.
Overview
- create CSS file
- enqueue CSS in block editor and in frontend
- ensure that
mobile:text-2xl
overrides style from theme.json
1. create CSS file
NOTE: don't forget the \
char
/* .../wp-content/my-theme/my-theme-custom-css.css */
@media (max-width: 600px) {
.mobile\:text-2xl {
font-size: 1.5rem;
}
}
2. Enqueue CSS in block editor and frontend
Load the CSS file in both editor and frontend
// in my-theme/functions.php
function my_theme_enqueue_blocks_assets() {
wp_enqueue_style(
'my-theme-custom-css',
THEME_URL . '/my-theme-custom-css.css',
);
}
add_action('enqueue_block_assets', 'my_theme_enqueue_blocks_assets');
Check if dom element read correctly the css class
In Frontend we have the class
In Block Editor we DON’T have the class
Remember, the iframe ???
Our CSS is loaded in the page, but not in the iframe that render the preview.
After 1 hours of research, i found an issue on Github that say that
”if your CSS doesn't contain .wp-block
and ..editor-styles-wrapper
it won't be loaded in the iframe".
Very weird and hard to understand.
So let's edit the CSS file like so
/* .../wp-content/my-theme/my-theme-custom-css.css */
.editor-styles-wrapper .dummy-enable-css-in-iframe ,
.wp-block .dummy-enable-css-in-iframe {
color: 'red';
}
@media (max-width: 600px) {
.mobile\:text-2xl {
font-size: 1.5rem;
}
}
Now the CSS is injected in the iframe
3. ensure that mobile:text-2xl
overrides theme.json style
Frontend
This is the fight
First we put !important
in our CSS...
/* .../wp-content/my-theme/my-theme-custom-css.css */
.editor-styles-wrapper .dummy-enable-css-in-iframe ,
.wp-block .dummy-enable-css-in-iframe {
color: 'red';
}
@media (max-width: 600px) {
.mobile\:text-2xl {
font-size: 1.5rem !important;
}
}
...but is not enough.
Maybe our custom CSS is injected before the one we want to override ??
So we tell to Wordpress that our custom css depends on the theme.json one, so that wordpress injects the theme.json one before.
// in my-theme/functions.php
function my_theme_enqueue_blocks_assets() {
wp_enqueue_style(
'my-theme-custom-css',
THEME_URL . '/my-theme-custom-css.css',
array('global-styles') // 'global-styles' is css generated from theme.json
);
}
add_action('enqueue_block_assets', 'my_theme_enqueue_blocks_assets');
Great! we won the fight
Let's go in the editor
What ?
Our CSS is not there anymore!
Maybe the “dependency” assets we added is not present in the editor?
Try to remove it
// in my-theme/functions.php
function my_theme_enqueue_blocks_assets() {
wp_enqueue_style(
'my-theme-custom-css',
THEME_URL . '/my-theme-custom-css.css',
// array('global-styles') // 'global-styles' is css generated from theme.json
);
}
add_action('enqueue_block_assets', 'my_theme_enqueue_blocks_assets');
...we are back in business !
Let's look how the theme.json css is loaded in page...
Mmm, it's not a <link>
like in the frontend but a <style>
, and there is no id
that i can use to search its "handle" name in the codebase.
It's time to fight again with specificity, let's see the fight
...we can’t inject my-theme-custom-css
after theme.json
one, se we need to be more specific in selectors to win, something like...
/* .../wp-content/my-theme/my-theme-custom-css.css */
.editor-styles-wrapper .dummy-enable-css-in-iframe ,
.wp-block .dummy-enable-css-in-iframe {
color: 'red';
}
@media (max-width: 600px) {
body.editor-styles-wrapper .mobile\:text-2xl {
font-size: 1.5rem !important;
}
}
... and we won!!!!
Let’s check in frontend...
...and sadly, our classes is not there anymore.
In frontend, body
doesn’t have editor-styles-wrapper
class, so we must add a second scoped selector in CSS only for frontend...
/* .../wp-content/my-theme/my-theme-custom-css.css */
.editor-styles-wrapper .dummy-enable-css-in-iframe ,
.wp-block .dummy-enable-css-in-iframe {
color: 'red';
}
@media (max-width: 600px) {
body.editor-styles-wrapper .mobile\:text-2xl,/* for editor */
body .mobile\:text-2xl /* for frontend */ {
font-size: 1.5rem !important;
}
}
Not so nice! But...
...it worked!
Considerations
Should be easies to inject a simple CSS. With Javascript ??
We should know that iframe receive CSS assets only if some CSS selector are present. This alone is big ALARM that maybe a refactor is necessary.
Inject CSS to the editor iframe should be done with a dedicated hook or similar process, and now is not present.
Core css specificity is high for an extendible system:
- in editor we must fight with a
scoped selector
and!important
- in frontend we must fight with
!important
It difficult to say to wordpress when to inject our custom css relative to the theme.json one. This lead us to increase even more the specificity of CSS selectors.
It impossible to add a medium amount of CSS without a CSS preprocessor that support nesting.
Imagine a child theme that want to overrides ?!?!?
Resources
https://github.com/WordPress/gutenberg/issues/48437 https://github.com/WordPress/gutenberg/issues/41821 https://github.com/WordPress/gutenberg/issues/38673#issuecomment-1036142814