Repository navigation
Tracking: Add a Style Engine to manage rendering block styles #38167
Description
This is a tracking issue for implementing a Style Engine as discussed in improving our saving/rendering of block styles. The goal is to have a consistent API for rendering styling for blocks, across both client-side and server-side applications. The server implementation is preferred for a site's frontend.
Rather than a monolithic refactor, the aim should be to build the smallest implementation possible and land it in the plugin, and iteratively enhance it with small PRs as we go.
In principle, the style engine should be able to receive a style object, and return the rendered styling for that style object, along with any required class names or other data needed to reassemble or use those styles in all required environments. Depending on the implementation, it may be possible for us to avoid or minimise the current reliance on rendering inline styles directly into block markup.
⭐ Current phase - Phase 1: block styles consolidation and refactoring the layout abstraction
Goals:
- To audit and consolidate where the code generates block support CSS in the backend so that they are delivered from the same place (as opposed to multiple places). This covers CSS rules such as margin and padding, typography, colors and borders.
- Removing repetitive layout-specific styles and generating semantic class names for each layout.
Planning discussions
- Style engine: functionality and design #42028
- Future ideas (not there yet)
To-do
The below to-do list is an assortment of shorter-term to longer-term features that would be great to implement. We should aim to not let the longer-term goals block landing the shorter-term goals — but let's try to use the longer-term goals to inform the design decisions for the shorter-term ones.
The approach will be work iteratively rather than treating this as a project that needs to be complete all at once.
- Think about how styles can still be indicated to theme/plugin authors via classnames: Explore options to add back semantic classnames to block wrappers #38719 and General CSS Concepts: Declaring
stylevs. describingstate#38694 - As a pre-requisite, ignore
styledifferences in block validation: Ignore style validation errors #37942 - Add an initial client-side version of the style engine. WIP PR: Add initial version of the style engine #37978
- Add a server-side implementation of the style engine. PR Style Engine: block supports backend #39446
- Move server-side implementation to package directory PR Style engine: move backend scripts to package #39736
- Consolidate generation of class names for rendered styles (e.g. replace each block support's ad hoc unique class name generation). Related issues: Use of
uniqid()in generated CSS class names breaks ability to cache parsed CSS #38889 - Add support for de-duping common or repeated styles so that there are no duplicate styles rendered. Related issues: Group block's generated CSS are redundant. #41434
- Implement mechanism for generated styles to be accessed for post content fetched via the REST API, to fix: Post Content: Layout styles not applied in editor (e.g. Social Icons, Buttons, etc.) #35376
- Come up with an inheritance model, e.g., Global styles > Elements > Block styles > Child block styles etc etc
- Allow generating styles for nested markup (e.g. calculating widths of child blocks, incorporating block gap of the parent as discussed here)
- Create a style engine glossary (documentation) Style engine: update terminology and docs #41964
- Refactor Global/theme.json styles to use the style engine (if at all relevant). Backend in
WP_Theme_JSONclass, and frontend inpackages/edit-site/src/components/global-styles/use-global-styles-output.js.See Style engine: output global styles CSS rules using selectors #40955 - Refactor padding support to use the style engine. Style Engine: block supports backend #39446
- Refactor margin support to use the style engine. Style Engine: add margin support to frontend #39790
- Refactor colors support to use the style engine.
- Refactor border support to use the style engine. Style Engine: add border to backend #40531 Style engine: add border to frontend #41803
- Refactor layout support to use the style engine.
- Refactor blockGap support to use the style engine.
- Refactor typography support to use the style engine. Style Engine: add typography and color to backend #40332 Style Engine: add typography and color to frontend #40665
- Refactor element (e.g. link) blocks support to use the style engine. Style engine: elements backend support #40987 Tracking: Add a Style Engine to manage rendering block styles #38167 Consider moving
ELEMENTSconst to style engine once we've dealt with JS global styles - Refactor duotone support to use the style engine.
- Explore adding support for nested style objects, scoped by pseudo class or media query (to add support for hover/active styles, or potentially different styles based on viewport size)
- Come up with a way to support custom selectors such that inner elements or pseudo states such as hover/focus/active can be targeted and adding support for responsive blocks / media queries. This should also help with styling compound components/blocks such as search, navigation, and table blocks (see styles for blocks with a wide design surface area). E.g., [WIP] Style engine: add elements and states to the backend #41619
- Support being able to render block styles in async contexts (for example, properly rendering block styles for post content fetched via the REST API, which will help address issues like: Post Content: Layout styles not applied in editor (e.g. Social Icons, Buttons, etc.) #35376)
- Create filters for CSS/style engine output and, possibly, general config options such as prettify (context) See also Proposal: Add More Ways to Filter Global Stylesheet Output #45322
Project board
We've started a project board for managing the above tasks over in: Gutenberg Style Engine (view)
Relevant discussions
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fields🏆 Done
Activity
ramonjd commentedon Jan 24, 2022
We already use cssnano, which can do a lot of this stuff.
From basic testing, it seems like if we stored the styles (in a store?) and then ran them concatenated through cssnano we might get something useful. I can take a look at this further.
youknowriad commentedon Jan 24, 2022
These two styles are different than the others in the sense that the generated styles shouldn't be applied to the "block wrapper" but instead to the "inner blocks wrapper". The difference is subtle since for most containers blocks, these two wrappers are the same but for something like Cover, they are not the same and that's why it's not possible to enable the current implementation of Layout bock supports for that block. (we need to first refactor the block support to apply to the inner blocks wrapper). I think that's something important to have in my mind while iterating on the style engine to avoid repeating the same mistake we have now.
[-]Tracking: Add a Styles Engine to manage rendering block styles[/-][+]Tracking: Add a Style Engine to manage rendering block styles[/+]andrewserong commentedon Feb 2, 2022
I believe the initial version of the style engine PR (#37978) is in a good place for wider feedback / review now, and to confidence check the approach. I've added a tiny comment/update to the PR.
andrewserong commentedon Feb 3, 2022
Just adding that there's been some discussion in #38277 and #27075 about exploring a standardized way to modify hover/focus/active states for blocks — it'd be good to keep an eye on these discussions to see if/when/how the style engine might slot into those explorations.
gziolo commentedon Feb 11, 2022
It looks like the automatically generated class names to move granular class names and styles outside of the block's HTML to the
styletag introduce some challenges. See #38719 and #38694. It would be great to keep that on the radar while working on the Style Engine implementation.There are vast benefits of the single automatically generated class name for a given block:
styletag instead of complex handling through__experimentalSkipSerializationflag in block support.However, it would be great to keep the flexibility of the current blocks that use several discrete class names to indicate selected block customizations like colors, fonts, etc.
andrewserong commentedon Feb 15, 2022
Update: the initial version of the style engine package has been merged in #37978, so we're now in a good place to start opening up smaller PRs to explore some of the ideas raised in discussions. I'm keen to have a play with how the server-rendering implementation might look.
Thanks @gziolo for linking in those related discussions. And good points about pollution of the HTML and targeting inner HTML elements. Since we're moving in a direction where most of the styles will ultimately be rendered server-side, hopefully that means if we expose discrete class names, it'll be fairly safe, because we'll be bypassing the issues of block validation and deprecations in post content.
Let's look at these issues in more depth in the discussions and related PRs.
fwazeter commentedon Feb 15, 2022
Glad to have found this tracking issue! Thanks @ramonjd for linking it! Which channel in slack is this stuff discussed in? Been looking for months for discussion around these issues!
Developed a CSS API via plugin to handle this exactly - taking sources from global styles, a CSS file (re-tooling some old PEAR PHP package for parsing CSS from css files), CPT etc, universalizes it then renders the styles inline, which was originally meant as a way to override the implementation of Layout Support (since we've been using FSE in production sites since 5.8 launched with the Gutenberg plugin).
I'd be happy to migrate the PHP classes to WP's coding standard (I've been writing in PSR style and tend to make smaller classes split up among files using interfaces & setter injection via containers) to get the ball rolling on a PHP side implementation.
In the plugin we made, we've been using a CPT as a data store for values, following the logic behind block-templates & navigation block. The flow is a little like this:
files from a repository class are sent to the CSS parser (e.g. from core blocks, fromAPI (wp_get_global_stylesheet), from CPT (e.g. internal), from JSON.
Parser outputs CSS into declaration block associative arrays, e.g.
Where the declaration block acts as the selector name.
Copy of these are saved as a stylesheet CPT with the corresponding declaration blocks
Stylesheet(s) are enqueued and rendered as inline styles.
for Layout Support, if block attr doesn't exist for (contentSize, wideSize, flex props, blockGap etc), then it creates a new CSS declaration block matching user input with a name suffix like __600px. If the style already exists in the stylesheet(s), rather than creating a new property, it simply uses the existing property.
adds the class to the block that has the support flag.
This is also done for overriding core block styles to deregister and reregister new default block styles based on user / developer preference, which is the main reason for the CPT - the CPT provides an easy front end interface to 'hot swap' values on the defaults.
So, for example, if I wanted to change all 'margin-left' properties of all blocks & auto generated styles (e.g. from layout support), to be margin-inline-start, then I can just change a single setting and the above API does all the work for me, replacing the corresponding properties, but not their values - or vice versa.
What we're also experimenting with is, rather than using a new class name for each user defined custom value (which doesn't happen often - 99% of the time a block using contentSize and wideSize is just using the theme default, we've literally overrode this with a custom value once across ~13 production sites in the wild.
For instance, if you assign contentSize and wideSize (or a flex property or whatever) to a custom property, and then add a css style that says "#make200" that sets the value of the customproperty to something different, then it'll change the custom prop just for that unique id - eliminating the need for a new class altogether.
Ultimately, the trickiest / most verbose part comes in expanding flex properties and a custom user setting, and so far the cleanest solution has just been to break up the overall declaration block into smaller declarations, but that does make for a long class="" line. In the end, that one just gets tricky if a box has an option to be both say, horizontal and vertical and have a variety of different options - you end up with a large matrix pretty fast.
Adding global / common alignment options for e.g. items-justified-center, etc as already exists seems to be the most prudent there - we just tap into those already declared classes rather than re-rendering them in a separate call.
For cases like row vs column, it's probably just more prudent to keep handling that via block style variants, such as is already done with row block & group block.
We also reduce the overall styling duplication with a couple smart global defaults, like:
* { box-sizing: border-box; }and then a hook into entry-content / main the_content tag for something like:The above allows us to remove any box-sizing property in core blocks, because they by default inherit border-box and inline elements aren't effected. Then the direct children only of the post_content area have padding visibly only on mobile completely preventing the problem of a paragraph block's text being flush with the side of the mobile device.
The slight tweak to .has-background on a paragraph element to make it still flush is like this:
This keeps the element in alignment as expected and not doubled up on padding via inheritance.
24 remaining items
ramonjd commentedon Jun 16, 2022
Thanks @andrewserong This neatly wraps up what I tried to say in a bazillion words!
There are lots of stepping stones when crossing this bridge, and some are bound to trigger poison darts! 😆
I still think the idea of eventually having an ideal outcome published somewhere might help guide us across.
mrwweb commentedon Jun 23, 2022
@ramonjd @andrewserong Thanks for the excellent replies! I look forward to the day the Style Engine brings us world peace and free ice cream every day!
I don't think a huge point-by-point response is needed (largely because your replies were so informative).
This totally makes sense. An accounting and consolidating makes total sense as a first step.
1000% percent. You've totally convinced me that early work in the back end also makes lots of sense, and so maybe the best metaphor is that we should know the starting point and ending point of this tunnel and the goal is meet the tunnels in the middle.
The primary goal of my last comment is not to say that incremental work is bad. I agree that it's probably the only way a task this large happens. Rather, the concern is that this journey will accidentally repeat past errors and lose folks' confidence along the way regardless of the final destination. Like I said:
That's why I recommend publishing a goal end point sooner than later. I think doing that can increase buy-in up front, identify risks of the path along the way, and find quick wins that can be implemented quickly once you're ready to start changing how the front-end code is shaped and output.
ramonjd commentedon Jun 24, 2022
Thanks for the feedback @mrwweb
I've just published a post that publicizes the rough roadmap towards better styles: https://make.wordpress.org/core/2022/06/24/block-editor-styles-initiatives-and-goals/
Much of it has been covered in this tracking PR, with the exception of the layout improvement, which I think will address a lot of the immediate pain.
The post deliberately doesn't refer to an end point because I don't know what that will look like yet, but I think we can imagine one, and agree that it would do more help than harm to discuss a wish list. 😄
fwazeter commentedon Jun 28, 2022
I've been working a lot on this in my own plugin, and like the broader effort here, am having a few issues with scoping the StyleEngine class (been mostly working on it from the PHP side for now).
Almost all of my effort so far has centered around the Layout Support module of Block Supports, since it's by far the biggest issue when it comes to implementing theme designs. Colors, Border, Typography is mostly fine as-is, and precision implementations can easily be overridden via skipping serialization on blocks that support the features.
Layout and spacing however, force a never-ending re-adjustment of what should be sensible global defaults. Just the nature of the beast.
One approach that I'm having success with in minimizing the amount of rendered CSS via PHP (that implementation proved very difficult to maintain and adjust over time natively), is to set a series of CSS custom props that render the dynamic values from merged styles.
My perspective has been so far that
theme.jsonworks best as a series of design tokens, and therefore basic values should be considered global CSS custom props. This leverages the strengths of Custom Props, the cascade & theme & user set values with easy overrides.For example:
These are then hooked into a basic global reset:
Solving the content-size & wide-size problem without dynamic properties & while respecting CSS flow / default layout
First, to override specificity of the universal selector above on HTML tags that are either commonly used as containers or as sectional elements that would be intended to expand in width, a basic selector in the reset follows like so:
Then, two utility classes to account for content-alignment wide & full:
Conceivably, an align-right & align-left class could simply adjust where the margin-property is autoing.
Importantly with the two align-wide and align-full utility classes, now a block, such as the group block, can be nested inside of another block with a different content-width setting and "bust out" of the container, respecting the new larger width value (100vw, & explicit content-wide value is used because % or "none" would simply match the parent container size), while maintaining it's place in the normal document flow.
This means we no longer need any repetition or redeclaration of the CSS classes or new stylesheets and they can simply pull from the same one, because the only dynamic value that's actually changed is the CSS Custom Property at the top level. Plus, child elements respect the content size of their parent containers by default and we don't need to do any goofy things like:
For the case of a user set value for layout width on a block, rather than generate a new inline style, the custom property is simply updated, like so:
This gives:
StyleEngine scope
This part is a bit messy. Generally, I've been tinkering with a two part API. One for generating CSS style declarations, and the other for retrieving, registering and rendering styles. So a single StyleEngine class is responsible for generating the styles themselves, and a StyleManager centralizes the sources of data, 'sanitizes' the output through the StyleEngine and outputs / registers with WP the result.
What I'm really aiming for is total separation between the Block Support API and style-generation or registration of any kind, simply providing the flags for the styles to 'hook' into, since we'd basically want to be able to at a glance see where every style is coming from in a centralized repo, without having to do research on why that one random file is appearing.
ramonjd commentedon Jun 29, 2022
Hi @fwazeter,
Thanks for the comment, and for taking the time to write down your thoughts!
The layout ideas might be also related to the proposals going on over at Tracking: extend layout options in Gutenberg as well.
This is close to the current thinking on the division of labour of any "style engine/manager".
So registering, storing, generating and outputting CSS style declarations and CSS rules.
See for example #41424 and #41896
The "centralized repo" part is where folks are concentrating efforts right now. See the Make Core post.
Beyond that, the hope is that it will make working on how to express, store and access styles a bit easier.
ramonjd commentedon Jul 6, 2022
Update Wednesday, July 6, 2022
The following updates are loosely grouped according to the Phases outlined in the recent Make Core post Block editor styles: initiatives and goals
Phase 1 (block supports and foundation building)
Work continues on creating equivalent style generation tools in JS for the block editor (non-frontend facing)
As 6.1 approaches there have been some PRs to improve operability and tighten up naming conventions:
WP_Style_Engine_CSS_Declarationsobject #42043Phase 2 (global styles consolidation and reducing style tags)
To achieve style tag reduction, we'll need a way to store CSS rules and enqueue stylesheets.
Given that, a discussion about the longer term architecture of the style engine is open: #42028
There have also been some experimental PRs that are testing boundaries and various approaches:
The first candidate for reducing style tags will be the layout abstraction as proposed in #40875.
As for global styles, there are a couple of experimental PRs looking at how to integrate the style engine:
Global styles contains many custom rules and style compilations. Furthermore, it's an area of active work and so the class changes every week.
One of the first tasks, therefore, will be to ensure we can introduce the style engine into global styles iteratively. For example, that we only parse certain top-level styles like
styles.spacingandstyles.typographyand nothing understyles.blocksorstyles.elementsyet.Phase 3 (to infinity and beyond! 🚀)
While this is beyond the scope of current work, there are things to keep in mind now.
In particular, how to store styles in a way that will allow us to control and output "layers" according to CSS cascade layers paradigm. See the description in #41424
ramonjd commentedon Aug 3, 2022
Update Wednesday, August 3, 2022
This update highlights important milestone PRs. A complete overview of completed and ongoing tasks is available on the Gutenberg Style Engine project board.
For more information about planned phases, see the Make Core post Block editor styles: initiatives and goals
Phase 1 (block supports and foundation building)
Much of the backend groundwork is now complete. Augmenting the JS package has taken a back seat to backend operability and code enhancement changes, e.g.,
There has also been a lot of movement on removing repetitive layout-specific styles, paving the way for the style engine to combine selectors and bundle a more optimized block supports CSS.
See:
Phase 2 (global styles consolidation and reducing style tags)
PRs to optimize CSS rules, enqueue styles and reduce the number of style tags have been merged 🎉
Work continues on refining the public methods, documentation and experimenting with global styles:
apeatling commentedon Aug 16, 2022
@ramonjd What's the latest on this one? I think work has wrapped for 6.1 on this?
ramonjd commentedon Aug 16, 2022
Aside from any incoming stability/code enhancement tweaks or bug fixes, there are no further plans to extend functionality until after 6.1.
The focus will be to assist with 6.1 migration and related core patches.
Merged plugin features that are planned for WordPress 6.1
Following the Block editor styles: initiatives and goals, Phase 1 is complete:
A major goal from Phase 2 has also been concluded:
Outstanding PRs
The JS version of the style engine covers all foundational block supports styles, i.e.,
spacing,colorandtypography.There is a lone PR to switch over to the style engine in the editor hooks.
This PR is optional and not required for 6.1.
Beyond 6.1
Imminent plans to complete Phase 2 and augment functionality include:
ramonjd commentedon Nov 18, 2022
Update Friday, November 18, 2022
The Style Engine is running in WordPress 6.1 🎉
Not much has happened since then aside from monitoring stability (there have been few, if no, bugs).
Completed since 6.1
In progress PRs
What's next
oldrup commentedon Nov 18, 2022
Nice work! It's a huge improvement overall.
mtias commentedon Jun 28, 2023
@andrewserong @ramonjd seems most of the items are concluded or no longer relevant. Should this be closed in favor of separate issues for remaining follow ups and ideas?
ramonjd commentedon Jun 29, 2023
Good call. I'll create a follow up tracking issue with the main items we still have to address (but have had not time/resources).
I'll close this for for now and link the follow up here when I get a chance to collate and publish.
Note to self, important items in the medium term: