gutenberg
gutenberg copied to clipboard
Proposal: Standardized block markup, theme.json design tokens, and CSS classes to improve interoperability
What problem does this address?
I have written a blog post describing the issues this proposal addresses in detail.
This is a proposal to use CSS utility classes for common layout needs and access to site-specific standardized design tokens set in
theme.json. Doing so will allow WordPress "core", themes, and plugins to have access to a valuable shared CSS toolkit. The result is a WordPress landscape where WordPress markup and styles are easier to extend, site content is more portable, and themes and plugins are more interoperable.
The overall goals are to:
- Ensure themes have sufficient ability to customize site designs
- Improve ability for plugins and theme switching to maintain user decisions
- Standardize the CSS for how core blocks accomplish frequent layout needs
The need for a clear direction and solution along these lines has come into focus recently:
It is clear that WordPress can provide a significant portion of front-end markup and styling responsibility, but this won't serve the community if the result is a "walled garden".
It’s been over three years since the block editor was released, yet theme authors are still unclear about what they can rely on in the block editor… Even among early and excited adopters of the block editor, I still frequently hear concerns and frustration from people who write custom themes and plugins about the lack of a consistent, and extensible front-end approach for blocks and their styles…
Now is a critical moment to find a path that meets the needs of WordPress core development without sacrificing the needs of 3rd-party themes and plugins.
The background and reasoning behind each decision along with examples are provided, so please read the full proposal if you want to leave a detailed comment.
This proposal brings together multiple different threads of conversations because I think it's important that each change is considered in the context of the others. When they come together, they illustrate a cohesive vision for a vibrant WordPress ecosystem benefiting all users and developers.
With a streamlined and transparent approach to design, core development will have a self-documenting, easy-to-understand set of tools for implementing future designs. When switching themes doesn't mean losing all your content design decisions or having to remove ones that no longer make sense, users gain more freedom with their content and trust WordPress even more. And when plugins can implement site-specific design tokens in their own output, they will work and look better for site owners and site visitors alike. Every site will be more than the sum of its parts.
What is your proposed solution?
This is a four-part solution that builds on existing practices already in WordPress while committing to full block/setting coverage and backwards compatibility:
- Ensure every HTML element in complex core blocks has a unique class (e.g.,
wp-block-media-text,wp-block-media-text__content,wp-block-media-text__media,wp-block-media-text__image) and use single-class selectors in core CSS when styling them so they are easy to override - Always communicate block state for all visual block options with CSS classes (existing examples:
is-vertically-aligned-center,has-background,is-style-{name},alignwide) - Standardize and extend
theme.jsondesign tokens. For example, have all themes definebackground,foreground,primary,secondary, andaccentcolors andgap-1,gap-2,gap-3,gap-4, andgap-5for gap values. Use these values as the standard options in block settings for the primary means of customization. I've written a proposed set of standard tokens to get this conversation going. - Output a single-purpose CSS utility class for each standard design token (point 3) and relevant block state (point 2), e.g.,
wp-color-background,wp-gap-4,is-vertically-aligned-center
This proposal is illustrated with code snippets in the blog post and there's a functioning demo showing a partial implementation.
I believe if these four things were done consistently and with a commitment to backwards compatibility, all of the following become easier or newly possible:
- Theme developers would happily write less CSS by setting standardized design tokens in
theme.json - Theme developers could confidently customize themes in places where UI settings are insufficient
- Better theme switching / content portability: Content design choices are not locked-in to a single theme's options or over-optimized to a specific theme's absolute values (e.g. choosing a "Large" gap rather than a
48pxgap is easier for users and would look good in any theme without modification) - Plugin developers would gain access to theme styles for their front-end output
- Core CSS would be more consistent across blocks
- Plugins, themes, and core could rely on a small, shared set of CSS utilities (resulting in less overall code sent to the browser)
Proposed Standard Design Token Names in theme.json
Themes and plugins could define additional tokens beyond these, but all of the following would be expected of new themes. WordPress would likely provide a fallback in cases where themes defined only some or none of the tokens.
- Colors:
foreground,background,primary,secondary,accent - Font sizes:
font-size-1,font-size-2,font-size-3,font-size-4(default),font-size-5,font-size-6,font-size-7 - Font Weights:
font-weight-1,font-weight-2,font-weight-3(default),font-weight-4,font-weight-5 - Font Families:
copy,headings,monospace - Borders:
border-1,border-2(default),border-3 - Gap:
gap-1,gap-2,gap-3(default),gap-4,gap-5 - Margin:
margin-1,margin-2,margin-3(default),margin-4,margin-5 - Padding:
padding-1,padding-2,padding-3(default),padding-4,padding-5 - Media Query Breakpoints:
two-columns,three-columns,desktop-menu - Content Widths:
contentSize(exists),wideSize(exists),maxSize
Things not included in the proposal
This proposal doesn't need to interfere with or be at the expense of:
- Inline styles or custom style blocks for one-off block styles (e.g.
7pxborder vs a small border) - Themes defining additional values beyond the standardized design token names
Related Issues
This is closely related to a number of existing issues, and tries to present a cohesive vision for a solution. Here's an incomplete list of current relevant issues:
- #38167 / #37495
- #38694
- #35470
- #33180
- #29568
- #35306
- #35141
- #38719
Credits / Attributions
None of the ideas in this proposal are new (a strength of the proposal). I think they are most compelling when packaged together. This builds on tons of existing thinking and work by other community members. Therefore, I think it's important to credit a many people here as I can.
- People who reviewed the blog post providing valuable feedback: @aurooba, @Luehrsen, @pattyok
- People who provided guidance and resources: @annezazu, @bph
- People whose writing/comments heavily influenced this post: @mtias, @richtabor, @cbirdsong, @luisherranz, @justintadlock, @billerickson
Thanks for all your thinking and work on putting this together @mrwweb - I wonder if this would be better as a github discussion rather than an issue in order to allow some threading of ongoing discussion on the various points?
@glendaviesnz I'm unclear about the best uses of Github discussions both in general and within the Gutenberg repository. I don't want it to get lost or buried—a couple really interesting discussions seem to have very little engagement. I'm open to whatever forum/format works best for generating robust discussion and moving these ideas toward adoption.
I'm unclear about the best uses of Github discussions both in general and within the Gutenberg repository. I don't want it to get lost or buried—a couple really interesting discussions seem to have very little engagement.
Good call, the discussion side is newer and doesn't seem to get as much visibility at the moment. Let's just leave it here for now for visibility as you say, we can always fork off separate discussions for specific areas if needed.
This is really useful feedback by the way, combined with the blog post. I have discussed it with the rest of my team this morning, and we are keen to come back to you with some feedback as it impacts some of the areas we are working on, but it may take us a day or two to think it all through properly.
This is something I asket months ago but no one answered me #33806
And here for naming things: #29568
I'm super excited about this idea in general. The lack of this kind of standardization has been a huge pain point with Gutenberg and honestly had us looking at other CMS options.
Some further thoughts from the perspective of an agency developer writing custom themes for clients:
Themes should be able opt out of using some or all of these design tokens.
A theme that is all in on bold typography should be able to remove lighter font weights from the UI, and should also be able remap those variables/classes to supported ones. This would ensure the site's existing content conforms to the new theme's design, along with any block patterns or plugins that use design token-based variables/classes.
Themes should be able to opt out of using any custom color/size values, including within existing content.
This should include any instance of the editor outputting inline styles, even stuff like column widths.
The themes we create basically never use custom size values, because we want the ability to guarantee design consistency and the ability to revise these values later. It also makes the editing experience much more approachable, as @mrwweb mentions.
The editor also needs to handle existing custom values much more elegantly. The way they work right now makes the block pattern directory essentially useless for themes that want to avoid custom values.
For example, existing custom font size values in site content continue being used, even when a theme that disables custom font sizes is active. The only way to know they're present is inspecting the front end, and the only way to remove them is to use the code editor view to delete them from the block by hand.
When the active theme has disabled custom values and the editor encounters an existing one it should either remove it, ignore it, or attempt to map it onto the closest preset value.
Core blocks need much more stable and conservative HTML and CSS.
Each core block's HTML and associated CSS need to be much more carefully considered for how it will fit into the larger theme ecosystem, and any change to markup or removal of CSS classes needs to be treated as a breaking change. The position outlined in The Block - Theme contract puts handcuffs on theme developers.
HTML structure and class names need to be implemented with an eye for overall usability and to avoid future breaking changes. It's ridiculous that the cover block refactor shipped with the class name .wp-block-cover__gradient-background in use for any background, with the more generic .wp-block-cover__background only being added in a maintenance release. This is the kind of thing that should be caught before it's merged into core.
Similarly, core block CSS should use a single class as the selector as often as possible (as mentioned by the OP), and when that's not possible any changes to CSS should take existing specificity into account and ensure it doesn't increase. Right now, many core styles use extremely elaborate selectors which are often difficult for theme authors to override, and these selectors often become more specific when updated, breaking existing theme customization. This will probably require additional (and possibly duplicated) classes throughout each block, but that's preferable to writing nightmarish selectors like .wp-block-cover.has-background-dim:not([class*=-background-color]), .wp-block-cover .has-background-dim:not([class*=-background-color]) or .wp-block-cover img.wp-block-cover__image-background,.wp-block-cover video.wp-block-cover__video-background.
"Intrinsic" responsive design should be used instead of media queries.
This has been discussed before:
- https://github.com/WordPress/gutenberg/issues/34641
Traditional media queries have always been an awkward fit for blocks since they almost never span the width of the browser window, and thus can't actually be adjusted based on their width on the page. For example, it's basically impossible for the same set of media queries to elegantly handle these two columns blocks, which are internally identical.
Container queries are the answer to this problem, but until they are widely supported enough core CSS should use "intrinsic" responsive design techniques as widely as possible. On my themes I've replaced the core column block's CSS with something inspired by Every Layout's Switcher, and it's generally much easier to deal with.
Themes should be able to offer preset "packages" of block options.
I dug into this in a separate issue, but the idea would also be appropriate for dimension values like spacing:
- https://github.com/WordPress/gutenberg/issues/39028
This feature and the ability to set multiple block styles (#14598) would address so many of the issues I run into when using core blocks.
Token additions and changes
(aka, getting into the weeds)
- Font sizes and weights: These should have more descriptive names. Otherwise, you're leaving too much up to the theme author's interpretation of what
font-weight-2means. For weights, the names in the editor dropdown right now seem pretty much fine. For sizes, Justin Tadlock's list on WP Tavern is a good start, though it would be good to have a convention around certain sizes being included that match default<h>tag sizes. - Font families: I would maybe rename "copy" to "base". I'd also add
display, which is commonly used in typography circles and other design systems to refer to a bigger heading size/style. - Colors: These are a good start, but I'd change
foregroundtotextand addaction, which can be a proxy for both link and button colors. I'd also suggest standardizing error/warning/success colors as suggested in @mrwweb's blog post, as plugins could use them and easily fit in with the current theme. - Line heights: I usually need a few options that are smaller than the basic body copy line height, but I'm not sure how I'd name these for normal users. Maybe "minimum" (meaning
1.0), "extra small", "small", etc? - Item widths: These would apply to columns, media/text and possibly buttons and images? You'd obviously need a set of percentages like 100%, 80%, 75%, 50%, 25% and 20%, but it should also support fixed sizes like "20rem" and more abstract sizes like "auto", "max-content" and "min-content". That way you can drop a column block with two columns in and set one as the "sidebar" with a width like "20rem" and let the main one grow to fill the rest of the available space with "auto".
- Container heights: Right now the only height option is the "full height" one on the cover block, and it's just a flat
100vh. It can be useful to have a variety of preset heights you can apply to a container block, and for those heights to be calculated values likemin(20rem, 60vh).
Some more discussion about this here https://wptavern.com/the-case-for-a-shared-css-toolkit-in-wordpress
I agree in principle to this and have been using a system similar to this for many years. It's essential to allow flexibility: using a key like --wp--gap--1 for 1rem makes it semantically impossible for a developer to implement a smaller key. xs, s, m, l, xl etc works well. Alternatively, using a scalar system like --unit--small: calc(var(--unit) / 2) also works well and helps to ensure a rhythmic design.
So, I'm just going to throw in my system for semantic naming below. I created much of this model based on Tailwind and discussion with other theme developers about three years ago. I have been using it since then (obviously without the --wp prefixes before theme.json existed). I have made some adjustments since then, but it has worked well overall.
It is not necessarily better or worse than any other system. But, it has at least given me some comfort not having to figure out what to name things. I can just plug in values and move onto other design issues I need to take care of.
The following is presented using CSS properties (e.g., --wp--preset--key--value), which would translate to the standard .has-* classes used by WP.
Font Sizes
For things where there is a sort of "middle" value that exists as the base, I prefer to use a T-shirt sizing system. This way, you can still semantically add more in either direction from the base.
--wp--preset--font-size--2-xs
--wp--preset--font-size--xs
--wp--preset--font-size--sm
--wp--preset--font-size--base // usually 16px
--wp--preset--font-size--lg
--wp--preset--font-size--xl
--wp--preset--font-size--2-xl
Line Heights
I almost always disable this for users and tie my line heights directly to the font sizes. I also disable the ability to customize the font size, so users are always getting a paired font size and line height.
However, if I am adding in specific presets, I would follow a stepped approach as explained the Margin/Padding/Gap section below.
Font Weight and Style
If adding custom classes for these, this should be simple to name. Just use follow what CSS does. Something along these lines:
--wp--preset--font-weight--100
...
--wp--preset--font-weight--900
--wp--preset--font-style--normal
--wp--preset--font-style--italic
Font Families
Most designs will have between 1 and 3 font families. More often than not, there is a specific font for headings, so I have separated it. Then, there is a primary/copy font for most other text on the site. And, in the case for secondary or less-used text, I have covered it too.
I also create a set of always-existing types just in case a user would, for example, want to always have some text in sans, serif, monospace, etc.
--wp--preset--font-family--headings
--wp--preset--font-family--primary
--wp--preset--font-family--secondary
--wp--preset--font-family--sans
--wp--preset--font-family--serif
--wp--preset--font-family--mono
--wp--preset--font-family--display
--wp--preset--font-family--handwriting
Margin / Padding / Gap
I typically use a stepped-number approach here:
--wp--preset--spacing--1 // 0.25rem
--wp--preset--spacing--2 // 0.5rem
--wp--preset--spacing--3 // 0.75rem
--wp--preset--spacing--4 // 1rem
I usually calculate these off a base number, so it's something like:
--wp--custom--spacing--base: 0.25rem;
--wp--preset--spacing--1: calc( 1 * var( --wp--custom--spacing--base ) );
I also usually add in a "global" spacing (sort of like blockGap but for everything):
--wp--custom--spacing--global: var( --wp--preset--spacing--8, 2rem );
One solution to this is allowing theme authors to declare the "spacing" values and WP automatically creating the .has-margin-2, .has-padding-4, .has-gap-8, etc. classes.
Content Width
We need a larger range of options than .alignwide and .alignfull. On almost every project I work on, I have a width somewhere in between those two, like an "extra-wide" size.
I'd definitely love to see a system with a range of sizes. I haven't really thought much on how to name these. But, I have turned to Tailwind's max-width classes often.
Colors
I think colors will be the toughest thing to agree on. There are so many different systems and methods. Plus, projects can vary so much in this area. It would be easier to create a standard naming scheme for other areas and not let colors become a blocker.
I closely follow the Tailwind model with colors. I create a primary, secondary, and neutral set of colors with that range from light to dark (100 - 900). In cases where I use a smaller range, I might skip the evens (200, 400, etc.).
--wp--preset--color--primary-100
--wp--preset--color--primary-200
--wp--preset--color--primary-300
--wp--preset--color--primary-400
--wp--preset--color--primary-500
--wp--preset--color--primary-600
--wp--preset--color--primary-700
--wp--preset--color--primary-800
--wp--preset--color--primary-900
--wp--preset--color--secondary-100
...
--wp--preset--color--neutral-100
...
I definitely agree on the need for extra width values beyond wide and full.
For margin/spacing/padding, I’d suggest going with -100, -200, -300 over -1, -2, -3, which leaves room for themes/plugins to add steps in between those values, which could be calculated using the surrounding -x00 values.
For font weight, however, I think using -100 step values is a bad idea since those shouldn’t directly correlate to weight values in a typeface and part of the value of these tokens is creating an abstraction layer. A pattern or plug-in should think in terms of using theme’s “semibold” weight, which could be font-weight: 500 in one and font-weight: 700 in another. Practically speaking I’m also not sure a coherent design would ever use more than four or five weights?
Color is definitely hardest. I see the merit in the Tailwind-style color model, but I’m not sure it’s the best fit for Wordpress/Gutenberg. Tailwind uses PurgeCSS to avoid shipping redundant unused CSS for colors that are never used, and there’s not really a good way for Wordpress to do that right now. Even discounting that, showing 9 color variations per color would get really overwhelming in the editor UI. I think it would be better to settle on a standard descriptive set of color names to use (ex.: text, background, highlight, accent, link/button/“action”, etc.), along with possible variations (ex: light/dark, maybe active/hover?).
This is fantastic! I made a similar issue related specifically to spacing awhile back: https://github.com/WordPress/gutenberg/issues/34210 (which I'm now going to close in favor of this one). Creating standard options for our users is really important, rather than just giving an open field with which they can adjust things like spacing using any value they like.
I also agree that colors shouldn't be gated behind this toolkit - naming conventions are just going to be all over the place.
Big thumbs up for this proposal!!! As a theme author I believe these kind of standardizations and improvements would make it much easier for all of us 😍
I would like to add that it would helpful to have added CSS classes for standard block supports like border styles, e.g. border-dashed, border-solid etc. as they are only added as inline styles at the moment. I hope it's ok to add this as a remark here.
Ensure every HTML element in complex core blocks has a unique class (e.g.,
wp-block-media-text,wp-block-media-text__content,wp-block-media-text__media,wp-block-media-text__image) and use single-class selectors in core CSS when styling them so they are easy to override
While we're at it, may I suggest using data attributes to style them accordingly? It might help solve some of our backward compatibility issues. We already have this data-type="core/media-text" attribute assigned to the root container of the block in the editor. I wonder if we would want to introduce something similar for styling purposes and make them public-facing APIs. For instance, data-wp-block="core/media-text", data-wp-block="core/media-text/media", data-wp-block="core/media-text/content", etc.
Since it's a new API, there should be no (minimal) compatibility issues, and it also makes it easier to distinguish between public and internal APIs, while most class names should be considered internal.
Or we can come up with a prefix for the class names to indicate clearly that they are public APIs, and anything else should remain internal for styling purposes only. I don't have any concrete idea for that yet unfortunately 😅 .
Please don't use data attributes for styling. Sure, it's possible, but that's what CSS class names are for. Data attributes should be primarily used for data handling.
“HTML5 is designed with extensibility in mind for data that should be associated with a particular element but need not have any defined meaning.”
“Custom data attributes are intended to store custom data private to the page or application, for which there are no more appropriate attributes or elements.“
As far as private vs public API I think it would make more sense to use the "style engine" to add generated classnames that could be used for the styling coming from core. But at the same time leave all the state describing class names as the public API.
So each block would have markup that looks like this:
<div class="wp-container-XXXXX wp-block-namespace has-background is-style-x ...">
...
</div>
See also #36135 which mentions the serious issues caused by the overly-specific inline CSS markup.
I wanted to note that this topic was discussed during today's Core Editor chat on Core WordPress Slack (requires registration).
One thing to note - there is a potentially related effort on a Styles Engine project which seeks to improve how block styles are handled.
I wonder if the CSS layers specification could be useful here:
/* Create the layers, in the desired order */
@layer base, theme;
@layer base {
/* Append to 'base' layer */
h1.title {
font-size: 5rem;
}
}
@layer theme {
/* Append to 'theme' layer */
h1 {
font-size: 3rem;
}
}
It isn't supported everywhere unfortunately, and some time might pass until it is.
I wonder if the CSS layers specification could be useful here:
Introducing layers would really simplify the specificity wars going on in the editor at the moment. But this would only be an amazing addition, not a replacement for what has been proposed here.
Thank you @mrwweb for putting the time and effort in gathering all these thoughts and looking through the history and past conversations. It's wonderful to see the attention and deep consideration here. My reply is going to be a bit long, so I apologize in advance.
One thing that doesn't seem explicitly clear in the issue formulation is that it's primarily focused in providing a better experience for the aspects a theme wants to customize that is not part of the user interface (the theme contract, the 80/20) as referred to in this conversation.
This is not so much a question of if but a question of how, since these tools need to find an interface expression that makes sense for users. Still, looking at the 80/20 split, it's likely some aspects of this setup are not exposed in UI or even in attributes, and it would be up to the theme to customize.
Let me know if I'm over-interpreting there! This is important context since achieving the right balance — one that allows the editor as a creative tool to flourish and themes to have the space to be as expressive as they need to be — depends on conceptualizing that properly.
That balance is quite tricky!
Ensure themes have sufficient ability to customize site designs
We should probably add "outside the UI" here since it aims to resolve the portion of style updates that is not handled by theme.json. In that regard we have to thread carefully with its implications because there are things currently not handled because they have not been implemented (for example, styling captions across blocks) and others not handled because they probably never will.
The architecture of styles that are meant to be handled by theme.json have additional requirements to those that are meant to be modified by themes alone. For example, they cannot be expressed exclusively as CSS variables because we need them to be more agnostic in its declaration in order to support them across web editors, front-end, mobile apps, etc.
This goes hand in hand with the consideration that changes a theme makes based on classes alone can be hard to transfer to other editing contexts. Even if in most cases editor styles can be supplied, mobile wouldn't know what to do with it. Furthermore, while editor styles can be supplied to editing contexts, it also implies the loading of CSS in anticipation of that CSS being used at all, which means sites would often be paying a performance penalty.
If we go about this wrong, themes also might end up thinking classes are a first-class API for the things that are meant to be covered declaratively. Another way of looking at this group is as the subset of style properties that are meant to be interoperable between the theme and the user. This interoperability is crucial because it also gives site maintainers the leverage to disable or restrict whatever it deems appropriate. Custom theme styles driven purely by the use of classes will be opaque to the user and the system as a whole. This is not a problem in itself when characterized as the things a user won't have UI access to modify anyways.
In that sense:
Recent changes to WordPress 5.9 appear headed in a direction where website styling outside of the WordPress-provided interface becomes more complicated and less reliable for people customizing front-end markup and styles.
We need to separate what may be temporary states from fundamental design principles. The container specific classes are mostly there to ensure we have a system that will be capable of handling specific styles rendered when a given block is actually on the page. It's not an indictment of how generic container primitives should work, which can and should for the most part have a more sensible or semantic class representation (whatever that is).
The list of issues captured in the longer post includes the removal of some relatively semantic classes that we should probably just treat as back-compat bugs.
Classes, including utility ones, are also opaque to the system. They can be an alright artefact but cannot function as an interoperable API. Utility classes are also tricky because they rely on certain conventions to work properly and can be disrupted easily by misuse. Generally, plugins will have higher success and a more stable foundation interacting with the json properties of the theme and the elements APIs and that should be recognized. (For example, an Ads plugin that wants to ensure certain elements are styled to what the theme / site needs using elements like text and links, both generally and block-specific.)
The situation with semantic classes is very nuanced as well. When we say “Large” or “+2” is more semantic than a discrete value we have to be careful around the user expectation. A user may not be choosing "Large" because of its semantic value but because it looks good to them. I think there is a distinction that may seem pedantic but is worth doing — there's some design tokens that are semantic and others that are merely encapsulations of discrete values. We need to be careful to not pass an encapsulation (which surely has value in terms of portability, etc, and we should still pursue) as a semantic element.
The color palettes split between default colors and theme colors is a good example, which you also touch upon in the appendix when it comes to how many colors makes sense to have semantic value (primary, secondary), when that stops making sense (quaternary, etc), and when the colored-labeled classes can start making more sense (particularly for patterns!). Even though these are defined as classes (vivid-green or whatever) they don't reflect a semantic value while theme colors aim to do so at least for the first few (primary, secondary). Side note, I kind of like the simplicity of theme-1, theme-2, etc.
This is also a layer orthogonal to the existence of Elements which are more clearly semantic (like link color and other properties like "headings"). The link element can be assigned a semantic color (such as primary) that is also used for other blocks (like a call to action Button) and would interoperate upon theme or theme.json style variations.
— There's a side note here related to the discussion in #38918 that speaks to these considerations: the colors defined in a theme palette might not actually be used for semantic elements, so they may not actually be changing how the site looks to a user! That's why the previews render the site background, text, link colors instead, which may or may not be mapped to theme palette colors, depending on the needs of the site, user, and theme.
I am in favor of establishing more sets of encapsulated values but I think it's important to not conflate Elements with these tokens / variables.
There's also another distinction to be made between what a value resolves to and what the UI might be exposing. This is super relevant for responsive typography, where what seems like discrete values might be resolving to calc functions or token primitives. The interface could have discrete controls yet still resolve to a tokenized value in some circumstances. The two may often be paired but not necessarily.
We also need to be careful with the proliferation of disconnected tokens. Often a "large" value for margin might not be actually adequate because it bears no declared relationship to other tokens that affect spacing. In some cases the token would need to be a composition of other primitives (like base-grid, or base-spacing unit, etc). This is obviously solvable, but we should be upfront about it.
A nice concrete example is the Space Increment property in a tool like https://hihayk.github.io/shaper/
In that sense, I'm not sure whether properties like gap-1 should be exposed or auto generated based on heuristics a theme can govern (like a space-ratio token or something that creates sets for paddings, margins, button sizes, gaps, etc).
The same applies to things like color-primary-100 or any hsl variations we might generate of them.
Improve ability for plugins and theme switching to maintain user decisions
This also touches upon what constitutes a user decision in practice. When a user picks a "green" color that is part of the theme "semantic" palette, are they picking it because they like the tone or because they have a semantic intention? Upon theme switching, would they expect it to change to the primary value of the new theme or be retained? This is in some ways largely unsolvable, but I think the separation of theme palette from color-specific palette has been a good step in providing users more clarity. We can still do better there.
Ensure every HTML element in complex core blocks has a unique class and use single-class selectors in core CSS
I tend to fully agree with the latter but not necessarily with the former, particularly in the details of its implementation. For example, I think we should avoid serializing classes for inner elements as much as possible and we should do it only when there's consistent semantic value being provided through them. This is hard to make into a general rule and would need to be more nuanced for each block core offers. It's also something hard to assume for third party blocks. Obviously the coupling it creates on markup shape would become more difficult to maintain so we need to ensure we are not solidifying poor or temporary markup choices excessively.
I think there's also something prior to that which is ensuring all blocks output .wp-block-{name} on the front-end even if not serialized (headings, lists, and paragraphs don't have them). This might need to be something that can be toggled on / off as needed. (People may also consider that p already provides all the semantic that is needed and that .wp-block-paragraph would noisy and redundant as an addition to it, and I'd agree.)
I know the proposal means to exclude these elements but I think we should revisit some of them. At least lists have caused some issues in the past since they are a very flexible element that is used to construct many different things and can be leaky.
In any case, the exception of p, h1-h6, ul, ol is an indication, from my perspective, that we need to look at the semantic markup of each block from its own perspective, and add or remove whatever makes sense semantically and pragmatically, not necessarily as a wide rule. Maybe we should do a project board and go through each block.
There's a delicate balance between giving themes granular hooks and deteriorating the semantics of user content. I agree with the reason "keep selector specificity of core CSS low" in general.
Communicating the full state of each block with CSS classes on the block wrapper element
This is alright, especially if it's not serialized!
Standardizing theme.json design token naming conventions and increasing the use of settings that use presets instead of absolute values
I'd generally say yes to this, with the caveat that design tokens are not necessarily semantic and we should not conflate the two things.
Elements might have a double representation as class names connected to a design token but both should be opaque to block authors. For example, "caption" should be stablished as an element that blocks can use as a component <Caption> which handles the implementation details of an eventual .wp-element-caption { color: --wp--element-caption }.
Creating a set of global, WordPress CSS utility classes intended for use by core blocks, themes, and plugins
Outputting utility classes as encapsulations for tokens is alright, but we should ensure we don't serialize them since they are just an implementation detail of block attributes and the styles engine. We should also be mindful that utility classes that have a representation in theme.json are better handled there rather than directly by a theme, even if the latter should be possible in the name of openness and flexibility.
Generally, while it'd be fine to establish some tokens and classes as an API for themes to extend in that last portion of customization outside of theme.json, it's also crucial to be upfront that they are not idiomatic to the block API on their own. With that I mean that a block author can add one of those without going through the proper mechanisms, which can make them inoperable in some conditions for no visible reason. That's why utility classes need to be an artefact, an implementation detail of other more declarative tools in the block API toolset, otherwise it'd be really hard to ensure proper functioning across platforms (editors, web, mobile).
Proliferation of utility classes can also become a burden if served as a monolithic stylesheet that is indifferent to the block style pipeline. Tools like Tailwind generally get around this problem through build steps that ensure only what's actually used gets enqueued. Classes that are generated by the style engine would have the same capability but a more naive approach that just gathered all of them in a .css file would not.
Finally, Background and Foreground are not meant to be color palette tokens but proper Elements defined as part of the block API (RichText, supports, etc). What a theme should be able to do is say elements.background: color.primary if that's what it needs to do, but it should avoid a color.background in its palette which is largely determined by context, which elements understand better than palettes do. Whether an element is represented as a CSS variable, for example, is an implementation detail. In other words, themes and plugins should interact with elements.text rather than a wp--color-text or wp--element-text even if the latter might also exist. This can be a subtle detail but is an important difference. We cannot ensure a variable is used correctly.
We can continue to discuss some of the holistic implication here but I think it'd be good to be extracting some actionable items already.
Thank you @mtias for these detailed notes. There is a lot of great insight in there and I really appreciate the perspective.
Ont thing that jumps out to me directly is that I would love to better understand what exactly you mean when you say:
Communicating the full state of each block with CSS classes on the block wrapper element
This is alright, especially if it's not serialized!
I'm probably missing some context somewhere but I'm not sure I fully understand what you mean by serialized in this instance.
themes and plugins should interact with elements.text
How? I don't think these values are exposed except through CSS inheritance. Should we output them as CSS variables?
@fabiankaegy definitely — with serialized I mean saving it in the markup compared to generating it based on attributes server-side at render time.
@scruffian with elements.text I mean accessing the theme.json object even if that value is represented as a variable down the pipe.
Thanks for starting this discussion. I wanted to share a couple of personal notes as well.
In general, and as a developer, I personally like when things are consistent and that similar conventions are used across the code base (blocks in particular here). It is true that the way the project evolves, we might have done some mistakes here and there that we try to correct from time to time, and I think it's good t discuss these bits one by one. I also think that having classnames reflecting block states in a more consistent way would be great (though with some flags, read later).
That said, I'd be very cautious about considering the classnames and markup as the primary APIs for themes to customize the output and provide semantics. Classnames are low level implementation details similar to how CSS variables can be or markup can be. They are tools provided by the platform to allow us to implement a design composed of "semantic" theme choices and "user" decisions. And often when translating a higher-level representation of these choices (theme.json, style object per blocks...) into the lower level implementation provided by the platform (CSS and markup for the frontend, React Native Styles for the mobile app), some level of semantic and choice is often lost in the translation.
Having the higher-level representation being the API adds value to the framework, the framework (WordPress) becomes aware of what is used and what's not used, becomes aware of how to switch representation without loosing content, or without loosing semantic styling. We can also implement things like "auto-minifying" the output without breaking the design, a valuable feature at the age of lighthouse scores (something we're getting close to and experimenting with for theme.json and block style objects), if the classnames become the APIs, it's very hard to optimize without breakage.
As a relative newcomer to working in the styles/design tools side of Gutenberg I have taken a bit of time to read through a lot of the background issues related to this proposal, and the amount of detail provided by people has been really helpful to get a wider understanding of all the issues at play here - so thanks to everyone that has taken the time so far to respond.
It seems like we are at a point in the discussion where we could split some things out into actionable items, so I am going to make some suggestions.
Although this proposal is mostly future-focused, it seems to me that some of the points raised have come out of frustrations that arose from changes in the 5.9 release. I thought it would be worth summarising those points in one place first, mostly for posterities sake, and I will follow up with a separate reply about the remaining forward-looking items, and some suggested action items related to them.
CSS/Theme/Styling Frustrations stemming from changes in 5.9
- The default setting of global values in themes without a theme.json is ignoring the add_theme_support defaults.
This has been fixed in 5.9.1, and the theme presets should now be used if they exist.
As noted in the above issue comment other alternatives were explored and this seems like the best approach. There is now a plugin available that removes this for affected themes as an immediate fix, but there is also an open issue discussing whether there can be an opt-out for developers, maybe as a theme.json flag, so it might be good to focus further discussion about this topic there.
- The replacement of layout specific class names with random classnames which broke themes that were targeting these class names to apply additional styling
There seems to be some agreement on the need to keep/return at least some of these. The desire, as noted by Matias, to avoid serialising these in the saved block content if possible means that trying to do this via the new Style Engine makes sense as a starting point, so an issue has been added here to track work and discussion around this.
Let me know if I have overlooked any of the 5.9 specific issues.
Suggested items to split off into separate issues
Always communicate block state for all visual block options with CSS classes (existing examples: is-vertically-aligned-center, has-background, is-style-{name}, alignwide)
As noted in my previous reply there seems to be enough agreement on this to continue work on it under the Style Engine project, and further discussion around it could happen on this issue.
Standardize and extend theme.json design tokens. For example, have all themes define background, foreground, primary, secondary, and accent colors and gap-1, gap-2, gap-3, gap-4, and gap-5 for gap values. Use these values as the standard options in block settings for the primary means of customization.
There also appears to be some consensus on the value of exploring this further, but it probably makes sense to handle it under the three distinct areas of color, typograpy and spacing, so I have added separate issues for Color, Spacing and Typography (If I have overlooked existing issues that would be a better starting point for these let me know).
For spacing, I had created a similar issue a while back:
- https://github.com/WordPress/gutenberg/issues/35306
As far as other stuff goes, @mrwweb had mentioned adding a maxWidth layout/content width size to go with contentSize and wideSize, which agree with. That would interact with some existing layout discussion in #36082 and #39336.
I also think there's a good case to be made for a set of "item width" presets that could be used for stuff like column sizing. I would rather users pick from a list of sizes (100%, 75%, 66%, 50%, 33%, 25%) instead of having to enter a percentage, and that would also allow for more fluid intrinsic design-style options like "auto", "max-content" and "min-content".
It took me a few days to digest some of the answers. (Also because I needed to re-read the answer from @mtias a few times and I'm still not sure if I understood everything correctly.)
We should probably add "outside the UI" here since it aims to resolve the portion of style updates that is not handled by theme.json.
I disagree with this. In my eyes making the UI a universal design tool on par with hand written, curated and designed styles is futile and wishful thinking. We've seen (a lot) of other products and editors try over the last decade and in the end they always fell short.
The limitations of the UI will always manifest itself in the designs created with it, resulting in what I would call the 'bootstrap' effect. In the end a reasonable experienced web user can spot websites build with these tools (Be it Webflow, Squarespace, Wix, or even Avada). There is even a 2016 joke website about this.
I agree that giving users the tools and methods to create own designs without code is an important and extremely worthwhile goal. But if the cost of doing that is limiting the freedoms of themes in the UI and UX of the frontend I fear dramatic consequences for the WordPress ecosystem.
Let's take a step back and remember, that "Classic Themes" aided WordPress to over 40% of CMS market share. Closed systems and walled gardens like Squarespace and Wix are certainly growing, albeit at a much smaller pace than WordPress is. So to me it does not seem that the vision of Phases 1 and 2 of the Gutenberg Project is not what all or even a majority of CMS users (currently) want.
If we look at the second fastest growing CMS Shopify, they are offering a decent DX with nearly limitless possibilities for CSS and JS and a somewhat limited Block Editor. Their growth seems to indicate, that their users are perfectly fine with the limited capabilities they have compared to the potential their sites have when using a proper theme.
I would like to emphasise that this fear of mine does come from a position of deep love for the WordPress project. We rant because we care!
We need to separate what may be temporary states from fundamental design principles. The container specific classes are mostly there to ensure we have a system that will be capable of handling specific styles rendered when a given block is actually on the page.
This is a huge issue with the Editor project in general at the moment. With a market share of over 40% when the WordPress project rolls out a feature, a defacto standard is inevitably created. As we've seen with 5.9 this sometimes comes at the cost of breaking a lot of live and in production sites.
There is no temporary when merging to core. Every time the project has short deprecations, has breaking changes or introduce bugs it has a cost: The reputation of the WordPress project. If something is temporary, it should live in the plugin until a final RC for a component can be merged into core. Everything else is playing with fire.
In general I am really happy that we're in agreement that a representation of state in the blocks classes are not off the table, as that makes theming a lot easier in general. Thank you for that.
I don't think we are in disagreement there! The tools from the editor are always going to be limited compared to the possibilities of custom work. Perhaps the wording wasn't clear enough but it was aimed at drawing a clearer objective between what the editor would do (limited and preset based tools) and what a theme should be able to expand upon (anything the web platform offers). In the scope of "Ensure themes have sufficient ability to customize site designs" we should clarify with "outside of the tools provided by the WordPress editor", because for the things that the editor does provide we should aim for interop and customizability out of the box (theme.json), and for the things the editor doesn't handle a theme should be free to extend with CSS of its own without restrictions and a good foundation (like classes and some tokens). The articulation between the two has several finer details we need to acknowledge, process, and instrument because they represent more of a spectrum rather than two entirely separate buckets.
Also with temporary measures I don't man breaking changes or deprecations, I mean that the feature-set of something like containers blocks is going to be iterative (for example, supporting position values like absolute, fixed, sticky, etc, that it doesn't currently expose) and its current footprint of container-specific classes is precisely to avoid establishing de facto standards for utility classes until it's clear what shape they should take.
...for the things that the editor does provide we should aim for interop and customizability out of the box (theme.json), and for the things the editor doesn't handle a theme should be free to extend with CSS of its own without restrictions and a good foundation (like classes and some tokens)
This is what I'm hoping can be achieved. Right now, Gutenberg sometimes fails spectacularly at both.
In regards to providing reasonable customizability out of the box, the lack of basic spacing options in theme.json akin to the way font size and color palette are defined is really frustrating. Being able to select basic padding and margin options for blocks is just as essential as being able to select font size and color, and right now we have to handle that via utility classes in our parent theme that we apply to blocks directly (effective, but not very client-friendly).
Similarly, there are many overbearing CSS selectors or !important flags in Gutenberg's CSS that make extending it difficult. For instance, our theme uses SASS to create minor variations of our colors in the color palette for things like hover on buttons (a reasonable extension of the base color palette functionality). But as of WP 5.9, all of the !important flags that are output onto the page via global-styles completely nuke our :hover selectors for those color variations on buttons.
There is no temporary when merging to core. Every time the project has short deprecations, has breaking changes or introduce bugs it has a cost
Gutenberg has had an enormously positive impact on how our agency develops sites. It gives us a ton of flexibility in content layout, and makes it much easier for clients to edit that content. But breaking changes have been a pretty consistent annoyance over the last year or so. I'd love to see a more measured approach to features as they're rolled out, especially when it comes to things like CSS selector specificity and the use of !important flags.
In other words, themes and plugins should interact with elements.text rather than a wp--color-text or wp--element-text even if the latter might also exist. This can be a subtle detail but is an important difference. We cannot ensure a variable is used correctly.
I'm not 100% sure what your intention here is for how block and themes could interact with theme.json, but here's an idea for a proposal to make that connection possible: https://github.com/WordPress/gutenberg/pull/39432