Skip to content

Tracking: Add a Style Engine to manage rendering block styles #38167

Closed

Description

@andrewserong

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

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.

Project board

We've started a project board for managing the above tasks over in: Gutenberg Style Engine (view)

Relevant discussions

Activity

  1. added
    [Type] Tracking IssueTactical breakdown of efforts across the codebase and/or tied to Overview issues.
    [Type] New APINew API to be used by plugin developers or package users.
    on Jan 24, 2022
  2. ramonjd commented on Jan 24, 2022

    @ramonjd
    Member

    Add support for de-duping common or repeated styles so that there are no duplicate styles rendered.

    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.

  3. youknowriad commented on Jan 24, 2022

    @youknowriad
    Contributor

    Refactor layout support to use the styles engine.
    Refactor blockGap support to use the styles engine.

    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.

  4. changed the title [-]Tracking: Add a Styles Engine to manage rendering block styles[/-] [+]Tracking: Add a Style Engine to manage rendering block styles[/+] on Jan 25, 2022
  5. moved this from Todo to No status in Gutenberg Style Engineon Jan 25, 2022
  6. moved this to 📖 Project overview in Gutenberg Style Engineon Jan 25, 2022
  7. andrewserong commented on Feb 2, 2022

    @andrewserong
    ContributorAuthor

    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.

  8. andrewserong commented on Feb 3, 2022

    @andrewserong
    ContributorAuthor

    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.

  9. gziolo commented on Feb 11, 2022

    @gziolo
    Member

    It looks like the automatically generated class names to move granular class names and styles outside of the block's HTML to the style tag 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:

    • no excessive pollution of the HTML for the block that simplifies validation and deprecation handling
    • the ability to target inner HTML tags through CSS syntax generated on the server with style tag instead of complex handling through __experimentalSkipSerialization flag 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.

  10. andrewserong commented on Feb 15, 2022

    @andrewserong
    ContributorAuthor

    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.

  11. fwazeter commented on Feb 15, 2022

    @fwazeter
    Member

    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:

    1. 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.

    2. Parser outputs CSS into declaration block associative arrays, e.g.

    "declaration_block" => array(
            "property" => "value"
            "property" => "value"
            ...
    

    Where the declaration block acts as the selector name.

    1. Copy of these are saved as a stylesheet CPT with the corresponding declaration blocks

    2. Stylesheet(s) are enqueued and rendered as inline styles.

    3. 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.

    4. 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.

    <style>
    --wp--style--content-size: 800px;
    
    .wp-container-default {
       max-inline-size: var(--wp--style--content-size);
    }
    
    #make200 {
    --wp--style--content-size: 200px;
    }
    </style>
    
    <div class="wp-container-default">
         <p>My container is 800px wide</p>
    </div>
    
    <div class="wp-container-default" id="#make200">
         <p>My container is 200px wide</p>
    </div>
    
    

    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:

    main > * {
    box-sizing: content-box;
    padding-inline-start: var(--wp--style--block-gap);
    padding-inline-end: var(--wp--style--block-gap);
    }
    

    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:

    /* in this implementation we were assigning the box-sizing prop to a custom prop in theme.json, with a default to border-box */
    p.has-background {
        --wp--custom--box-sizing: content-box;
        box-sizing: var(--wp--custom--box-sizing);
    }
    
    

    This keeps the element in alignment as expected and not doubled up on padding via inheritance.

  12. 24 remaining items

  13. ramonjd commented on Jun 16, 2022

    @ramonjd
    Member

    open-ended or large scale projects that never manage to ship because the scope is too big. I think part of the rationale for working iteratively on existing features, and fix bugs along the way, is so that the code changes have immediate code quality / stability value in the short-term, without becoming too monolithic of a project.

    the current work will make it easier for us to change the final output, whatever it'll be. For me, having a goal be "let's make this whole system easier to change" seems to be one that lines up with whatever decisions or proposals folks make for how we think styles should ultimately be output.

    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.

  14. mrwweb commented on Jun 23, 2022

    @mrwweb

    @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).

    Right now, my efforts, at least in relation to the style engine, are focussed on consolidating where we generate styles.

    This totally makes sense. An accounting and consolidating makes total sense as a first step.

    I still think the idea of eventually having an ideal outcome published somewhere might help guide us across.

    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.

    I'm trying to get to a point where the style engine can incrementally improve the situation.

    I think part of the rationale for working iteratively on existing features, and fix bugs along the way, is so that the code changes have immediate code quality / stability value in the short-term, without becoming too monolithic of a project.

    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:

    A lot of the planned phases seem to move directly through a ton of known pain points for themers such as inline styles (and therefore missing semantic classes), mostly redundant style blocks, etc.

    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.

  15. ramonjd commented on Jun 24, 2022

    @ramonjd
    Member

    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.

    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. 😄

  16. fwazeter commented on Jun 28, 2022

    @fwazeter
    Member

    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.json works 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:

    :body {
       --wp--style--content-size: <merged data>
       --wp--style--wide-size: <merged data>
    }
    

    These are then hooked into a basic global reset:

    /* among a few other very basic properties not 
    relevant here, note that I place fallback 
    values but likely unnecessary 
    */
    
    * { 
        max-inline-size: var(--wf--content-size, 60ch) 
    } 
    

    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:

    html, body, div, article, 
    header, main, footer, nav, section {
           max-inline-size: none;
    }
    

    Then, two utility classes to account for content-alignment wide & full:

    .align-wide {
          inline-size: var(--wp--style--wide-size, 70rem);  
          margin-inline-start: calc(50% - calc(var(--wp--style--wide-size, 70rem) / 2));
    }
    
    .align-full {
         inline-size: 100vw;
         margin-inline-start: calc(50% - 50vw);
    }
    
    /* to center content by default, 
    such as the default setting for WP 
    with contentSize 
    */
    
    .align-center {
          margin-inline: auto;
          /* for fun, we could make it box-sizing: content-box; 
             & add padding-inline start & end to fix global padding 
             from theme.json 
          */
          box-sizing: content-box;
          padding-inline-start: var(--wp--style--padding, 1rem);
          padding-inline-end: var(--wp--style--padding, 1rem);
    }
    
    

    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:

    <group block contentWide>
         <group block inheritDefault Layout>
               <group block contentFull>
    

    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:

    /* personally, I've been experimenting with adding a 
     [data-wp-styles] attribute to the tag and a basic check 
    if the property already exists, don't create it again 
    */
    
     <group block style="--wp--style--content-size: <custom value>;"
    

    This gives:

    • Themers & Plugins a defacto 'hook' via the custom prop.
    • Honors the cascade at all levels
    • Is easy to override by user, theme or plugin preferred values because there are multiple ways to override the style.
    • Radically simplifies Layout Support part of Block Support API & makes the styles / codes easier to maintain

    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.

  17. ramonjd commented on Jun 29, 2022

    @ramonjd
    Member

    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.

    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.

    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

    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.

    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.

  18. ramonjd commented on Jul 6, 2022

    @ramonjd
    Member

    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:

    Phase 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.spacing and styles.typography and nothing under styles.blocks or styles.elements yet.

    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

  19. ramonjd commented on Aug 3, 2022

    @ramonjd
    Member

    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:

  20. apeatling commented on Aug 16, 2022

    @apeatling
    Contributor

    @ramonjd What's the latest on this one? I think work has wrapped for 6.1 on this?

  21. ramonjd commented on Aug 16, 2022

    @ramonjd
    Member

    What's the latest on this one? I think work has wrapped for 6.1 on this?

    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:

    • 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

    A major goal from Phase 2 has also been concluded:

    • Reduce the number of inline style tags we print to the page for block, layout and elements support.

    Outstanding PRs

    The JS version of the style engine covers all foundational block supports styles, i.e., spacing, color and typography.

    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:

    • Connect global styles to the same mechanism with which we’re generating block styles.
    • Improve pre-render CSS rule processing with the intention of deduplicating other common and/or repetitive block styles.
    • Establish and document standards by which to extend/override CSS, e.g, filters and so on
    • Extend the scope of semantic class names and/or design token expression, and encapsulate rules into stable utility classes.
  22. ramonjd commented on Nov 18, 2022

    @ramonjd
    Member

    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

    • Early planning of global styles integration, breaking down tasks and so on. (To be continued in early 2023)
  23. oldrup commented on Nov 18, 2022

    @oldrup

    Nice work! It's a huge improvement overall.

  24. mtias commented on Jun 28, 2023

    @mtias
    Member

    @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?

  25. ramonjd commented on Jun 29, 2023

    @ramonjd
    Member

    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

    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:

    • Refactor blockGap support to use the style engine.
    • Style engine: extend block support style definitions #45296
    • Next big thing is global styles/theme.json - it's always lingering at the back of my mind, then 🤞🏻 the sky is the limit 😄
    • Look into filters for CSS/style engine output and, possibly, general config options such as prettify
  26. moved this from 📖 Project overview to 🏆 Done in Gutenberg Style Engineon Jun 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    [Package] Style Engine/packages/style-engine[Status] In ProgressTracking issues with work in progress[Type] New APINew API to be used by plugin developers or package users.[Type] Tracking IssueTactical breakdown of efforts across the codebase and/or tied to Overview issues.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    Sponsor
    SponsoredKunjungi sekarang
    Promo