Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Standardized block markup, theme.json design tokens, and CSS classes to improve interoperability #38998

Open
Tracked by #39324
mrwweb opened this issue Feb 22, 2022 · 66 comments
Labels
CSS Styling Related to editor and front end styles, CSS-specific issues. Developer Experience Ideas about improving block and theme developer experience [Feature] Themes Questions or issues with incorporating or styling blocks in a theme. Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Type] Discussion For issues that are high-level and not yet ready to implement.

Comments

@mrwweb
Copy link

mrwweb commented Feb 22, 2022

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:

  1. Ensure themes have sufficient ability to customize site designs
  2. Improve ability for plugins and theme switching to maintain user decisions
  3. 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:

  1. 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
  2. Always communicate block state for all visual block options with CSS classes (existing examples: is-vertically-aligned-center, has-background, is-style-{name}, alignwide)
  3. 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. I've written a proposed set of standard tokens to get this conversation going.
  4. 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 48px gap 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. 7px border 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:

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.

@annezazu annezazu added [Feature] Themes Questions or issues with incorporating or styling blocks in a theme. [Type] Discussion For issues that are high-level and not yet ready to implement. Developer Experience Ideas about improving block and theme developer experience Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json CSS Styling Related to editor and front end styles, CSS-specific issues. labels Feb 22, 2022
@glendaviesnz
Copy link
Contributor

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?

@mrwweb
Copy link
Author

mrwweb commented Feb 23, 2022

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

@glendaviesnz
Copy link
Contributor

glendaviesnz commented Feb 23, 2022

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.

@overclokk
Copy link

This is something I asket months ago but no one answered me #33806

And here for naming things: #29568

@cbirdsong
Copy link

cbirdsong commented Feb 23, 2022

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:

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:

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-2 means. 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 foreground to text and add action, 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 like min(20rem, 60vh).

@glendaviesnz
Copy link
Contributor

Some more discussion about this here https://wptavern.com/the-case-for-a-shared-css-toolkit-in-wordpress

@markhowellsmead
Copy link

markhowellsmead commented Feb 26, 2022

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.

@justintadlock
Copy link
Contributor

justintadlock commented Feb 27, 2022

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

@cbirdsong
Copy link

cbirdsong commented Feb 28, 2022

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

@eric-michel
Copy link

This is fantastic! I made a similar issue related specifically to spacing awhile back: #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.

@ellenbauer
Copy link

ellenbauer commented Mar 5, 2022

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.

@kevin940726
Copy link
Member

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

@markhowellsmead
Copy link

markhowellsmead commented Mar 8, 2022

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

@fabiankaegy
Copy link
Member

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>

@markhowellsmead
Copy link

See also #36135 which mentions the serious issues caused by the overly-specific inline CSS markup.

@getdave
Copy link
Contributor

getdave commented Mar 9, 2022

@adamziel
Copy link
Contributor

adamziel commented Mar 9, 2022

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.

@Luehrsen
Copy link
Contributor

Luehrsen commented Mar 9, 2022

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.

@mtias
Copy link
Member

mtias commented Mar 10, 2022

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.

@fabiankaegy
Copy link
Member

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.

@tellthemachines
Copy link
Contributor

Hey peeps 👋 there's a lot very thoughtful discussion here around CSS classes and naming in general, so I thought it might be a good place to seek some feedback on my experiment in #42763, where I'm moving the content width logic into its own layout type, to make it possible for blocks to apply content width to their children by default.

The main thing I'm seeking feedback on is the name for this new layout type: I provisionally called it "column" but don't really like that as it can be confused with the Column block. I'm currently leaning towards "center", because what that layout type does is create a center-aligned column of content. It would be great to have some more thoughts on this!

For reference, the layout type name is exposed as a block classname (is-layout-[name]) and can be used in block.json when defining a specific layout type for a block.

@ghost
Copy link

ghost commented Aug 9, 2022

Thank you for your great work!
If it is called "center", what would the other options for the layouts be? And what would they look like?

I still favour a clean separation between a width and an alignment property to be able to do something like the below image shows:

Screenshot 2022-08-09 at 18 06 51

@tellthemachines
Copy link
Contributor

If it is called "center", what would the other options for the layouts be? And what would they look like?

Good question @sascha-bleech . Currently, apart from 'default', we also have 'flex', which has horizontal and vertical variations, and a bunch of alignment options too. In the future we may have other layouts such as 'grid', and perhaps something that allows for absolute or fixed positions.

What we're trying to do with 'center' is split out the content width logic that currently is part of 'default' into its own layout, so that it's possible for blocks to have content width enabled out of the box.

I still favour a clean separation between a width and an alignment property to be able to do something like the below image shows:

That's an interesting idea! The current work won't impact the possibility of implementing something like that in the future, though the name 'center' might not be the best if we were to enable right or left alignment for the content.

It's hard to find a good single word description for this 😅

constrain would be another possibility, though it may not be immediately obvious what it means.

@ZebulanStanphill
Copy link
Member

Yeah, "constrain" or (preferably) "constrained" sounds like a much more descriptive title to me... at least after you realize what it means, anyway. There's certainly a lot less room for confusion in the long-run than a name like "column".

@ghost
Copy link

ghost commented Aug 11, 2022

Good question @sascha-bleech . Currently, apart from 'default', we also have 'flex', which has horizontal and vertical variations, and a bunch of alignment options too. In the future we may have other layouts such as 'grid', and perhaps something that allows for absolute or fixed positions.

The other options/variations all sound very CSS like: flex, grid, fixed, absolute...
What about calling it "block" to keep with the CSS nomenclature? Or maybe "theme" as it seems to be the default layout which is defined in the theme?

"Constrain" sounds too negative/constrained to me ;-)

@tellthemachines
Copy link
Contributor

Thanks for the feedback everyone! I decided to go with "constrained" as it best describes the specific layout type.

The other options/variations all sound very CSS like: flex, grid, fixed, absolute...

That wasn't on purpose; it would be preferable not to name them after their implementation details 😅

@mrwweb
Copy link
Author

mrwweb commented Aug 16, 2022

@tellthemachines I'm late to the party, but hopefully not too late!

The main thing I'm seeking feedback on is the name for this new layout type:

I wonder whether the answer is simpler than that and more or less answered:

I'm moving the content width logic into its own layout type, to make it possible for blocks to apply content width to their children by default.

As many people have pushed hard for on #33374, I think there's a general feeling that the centered and constrained alignment should be the default alignment both for top-level blocks and container blocks like Group, Cover, and even Column blocks. So for a name, I would propose "default", and because of that I wouldn't expose that as a UI option if it can just be the default state of all block containers.

Giving this a name (and a class that can be applied to block containers) is an awesome idea and so I'm glad you're doing that work!

@eric-michel
Copy link

@mrwweb

I would propose "default", and because of that I wouldn't expose that as a UI option if it can just be the default state of all block containers.

I proposed the same here: #42763 (comment). There was a bit more conversation beyond that you might find informative. It seems default unfortunately isn't in the cards, but it does seem that we're getting the theme.json layout settings applied to container blocks by default, so at least that will resolve our issues with adopting theme.json.

@krokodok
Copy link
Contributor

krokodok commented May 30, 2023

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.

Cascading layers seem to be widely supported by now: https://caniuse.com/css-cascade-layers

Encapsulating all WordPress/Gutenberg-generated CSS into layers would make custom theme development so much easier as we would not need to match the specificities all the time.

Because layered CSS cannot overrule non-layered CSS developers can't benefit from cascade layers until the core uses them as well.

@mrwweb
Copy link
Author

mrwweb commented May 30, 2023

@krokodok Cascade layers feels like a good new issue to open and would likely help with some of the goals mentioned in this thread. I'd encourage you to open that and bonus points for summarizing the conversation that's already happened on other issues and one discussion.

@mrwweb
Copy link
Author

mrwweb commented May 30, 2023

With the spacing scale (#35306) now out in the wild, I wanted to show how it can make themes and plugins more interoperable today. I hope that plugins take advantage of techniques like this and folks will be inspired to further action on standardizing colors, font-sizes, etc.

Background: I have a set of files I use to make The Events Calendar (TEC) plugin inherit styles from my themes. Because TEC uses custom properties, I can override their values to match my theme better.

Instead of using the plugin default, I can remap the TEC spacing values to use the theme.json spacing scale. So with very few edge-cases, almost any theme should be able to have TEC use their spacing scale with this code:

body {
    /* Remap The Events Calendar spacing properties to theme.json presets */
    /* custom property fallbacks are the values used in TEC */
    /* Note: spacing scale is slightly collapsed to accommodate the number of default spacing presets */
    --tec-spacer-0: var(--wp--preset--spacing--10, calc(var(--wp--preset--spacing--20) / 2), 4px);
    --tec-spacer-1: var(--wp--preset--spacing--20, 8px);
    --tec-spacer-2: var(--wp--preset--spacing--30, 12px);
    --tec-spacer-3: var(--wp--preset--spacing--40, 16px);
    --tec-spacer-4: var(--wp--preset--spacing--40, 20px);
    --tec-spacer-5: var(--wp--preset--spacing--50, 24px);
    --tec-spacer-6: var(--wp--preset--spacing--50, 28px);
    --tec-spacer-7: var(--wp--preset--spacing--60, 32px);
    --tec-spacer-8: var(--wp--preset--spacing--60, 40px);
    --tec-spacer-9: var(--wp--preset--spacing--70, 48px);
    --tec-spacer-10: var(--wp--preset--spacing--70, 56px);
    --tec-spacer-11: calc((var(--wp--preset--spacing--70, 56px) + var(--wp--preset--spacing--80, 80px)) / 2);
    --tec-spacer-12: var(--wp--preset--spacing--80, 80px);
    --tec-spacer-13: var(--wp--preset--spacing--90, calc(var(--wp--preset--spacing--80, 80px) * 1.2));
    --tec-spacer-14: var(--wp--preset--spacing--100, calc(var(--wp--preset--spacing--80, 80px) * 2));
}

There are some specifics to the above code where I've had to do some things to map a smaller theme.json scale to a wider range that TEC expects with some collapsing and averaging, but I'm overall quite pleased with the results in my testing so far.

What's exciting to me is that there's no reason The Events Calendar couldn't just do this themselves and have their plugin styles automatically match the default spacing of a theme. Plugins can start doing this today as long as they provide fallbacks. Default WordPress comes with 20 - 80 defined on the spacing scale. Form plugins, events plugins, slideshow plugins, etc, could all really benefit from using this. --wp--style--block-gap is another great candidate that plugins could use immediately with a fallback (for instance a gallery plugin).

TEC similarly uses an "accent" color which would be a prime candidate for grabbing directly from a palette of standardized color slugs. Interestingly, TEC would also benefit from the ability to easily reference additional values like settings.elements.h3.fontSize.

Conclusion: I hope this shows off the value of standardizing "design tokens" via theme.json. We should celebrate the spacing scale! Let's find ways to make this work even better with colors and font sizes!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CSS Styling Related to editor and front end styles, CSS-specific issues. Developer Experience Ideas about improving block and theme developer experience [Feature] Themes Questions or issues with incorporating or styling blocks in a theme. Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Type] Discussion For issues that are high-level and not yet ready to implement.
Projects
None yet
Development

No branches or pull requests