WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.5k stars 4.19k forks source link

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

Open mrwweb opened 2 years ago

mrwweb commented 2 years ago

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:

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.

Things not included in the proposal

This proposal doesn't need to interfere with or be at the expense of:

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.

glendaviesnz commented 2 years ago

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 commented 2 years ago

@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 commented 2 years ago

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 commented 2 years ago

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

And here for naming things: #29568

cbirdsong commented 2 years ago

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)

glendaviesnz commented 2 years ago

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

markhowellsmead commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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.

ellenbauer commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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

getdave commented 2 years ago

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.

adamziel commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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.

scruffian commented 2 years ago

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?

mtias commented 2 years ago

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

youknowriad commented 2 years ago

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.

glendaviesnz commented 2 years ago

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

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

  1. The addition of ‘!important’ to user styles.

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.

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

glendaviesnz commented 2 years ago

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

cbirdsong commented 2 years ago

For spacing, I had created a similar issue a while back:

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

Luehrsen commented 2 years ago

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.

mtias commented 2 years ago

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.

eric-michel commented 2 years ago

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

scruffian commented 2 years ago

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

landwire commented 2 years ago

I had not the time to read all of this thread yet, but I would like to see consistency in the naming of the variants of property values, if at all possible and where it makes sense. I find just numbers 1, 2, 3, 4 etc. too abstract, but prefer the T-shirt sizing system that @justintadlock mentioned here: https://github.com/WordPress/gutenberg/issues/38998#issuecomment-1053727408

So using a standard set: xxs, xs, s, m, l, xl, xxl or a subset thereof, if less is needed. Not sure if we need an explicit none/zero value for gap, margin, padding?

Font sizes: font-size-xxs, font-size-xs, font-size-s, font-size-m, font-size-l, font-size-xl, font-size-xxl Gap: gap-none, gap-xs, gap-s, gap-m, gap-l, gap-xl Margin: margin-none, margin-xs, margin-s, margin-m, margin-l, margin-xl Padding: padding-none, padding-xs, padding-s, padding-m, padding-l, padding-xl

Width could really follow the same pattern and I definitely think, like some others in this thread, that we need more than just "content" and "wide" widths: smallSize, mediumSize, largeSize, extraLargeSize

eric-michel commented 2 years ago

I find just numbers 1, 2, 3, 4 etc. too abstract, but prefer the T-shirt sizing system that @justintadlock mentioned here: https://github.com/WordPress/gutenberg/issues/38998#issuecomment-1053727408

I like this system too, especially because you can theoretically continue adding options in either direction.

Width could really follow the same pattern and I definitely think, like some others in this thread, that we need more than just "content" and "wide" widths: smallSize, mediumSize, largeSize, extraLargeSize

I'd love to have a configurable set of width options. In most cases, full, wide, and standard are sufficient for our sites, but we do define an alignnarrow class that we frequently use. But since it's not easy to add to the alignment selection UI, we have to add it as a custom class. Just being able to add more options would be great.

As an aside, I worry a lot about @Luehrsen's comment:

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 see a ton of value in creating sensible base standards for all of these properties, especially when it comes to things like plugins. A form plugin, for instance, will be able to reliably use the accent color for the Submit button, rather than some randomly chosen color by the plugin authors. But I do worry that creating such a standard will result in less variety of theme design. With 5 defined colors that offer interoperability with lots of big name plugins, how often will themes go outside of that 5-color dynamic, even if the option is available?

markhowellsmead commented 2 years ago

Width could really follow the same pattern and I definitely think, like some others in this thread, that we need more than just "content" and "wide" widths: smallSize, mediumSize, largeSize, extraLargeSize I'd love to have a configurable set of width options. In most cases, full, wide, and standard are sufficient for our sites, but we do define an alignnarrow class that we frequently use. But since it's not easy to add to the alignment selection UI, we have to add it as a custom class. Just being able to add more options would be great.

Completely agree that we need configurable widths. However, you can add alternative settings using a block filter, if you need e.g. an alternative width when the core setting “wide” is selected.

https://github.com/SayHelloGmbH/Gutenberg/blob/master/blockfilter/group-alignwide/index.js

mrwweb commented 2 years ago

Thanks to everyone who has weighed in so far. Amazing ideas and energy here! (Pardon my delay in responding. I've been away from the computer for a few weeks.)


@glendaviesnz's recap comment:

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

Thanks for the good recap. What I really want to emphasize with those examples is that there doesn't seem to be a clear set of standards that themers understand or core devs follow to avoid these types of issues. They show the need for some simple public well-known conventions that everyone can trust and follow.

And:

Suggested items to split off into separate issues

Those separate issues are a great start!


@mtias:

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

I agree that it's primarily focused on theme customization, but the token-driven settings for users (spacing, weights, sizes) is an important UI change that would be required to fully implement the design token proposal. For example, I envision a block "Gap" setting that is a drop down of presets rather than a pixel size input.

Because of that, I'm not sure if this was right: "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." They would be very clear to the user because they'd be reflected in block settings. If you're more referring to the use of these classes in things like block patterns, then I suppose that's clear, but it would also enable something that has been impossible so far: patterns inheriting theme values in their designs. That seems like a good tradeoff.

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.

It seems to me that as soon as markup lands in a released version of WordPress themes are customizing it and assuming it's going to be maintained. I think that assumption is fair. I don't think that adding classes to block HTML would somehow make things that markup or expectations for it any different. All it would do is make themers' lives a lot easier. If markup changes, I would much rather be changing some classes than redoing more complex element nested selectors.

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.

I think that the direction of theme.json is super compelling and adding this layer of standardization would make it even more compelling! It feels to me that a carrot approach (rather than a stick) will work to get people using theme.json, but there shouldn't be anything that can only be styled through theme.json or is made harder "because you should do that with theme.json". That's what I'm really trying to do here. I am personally doing my best to adopt theme.json… and it will never be a full replacement for CSS.

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.

Is there a different way that plugins are expected to access theme.json values from a theme for use in their styles? I am not aware of that.

One assumption I'm making is that if plugins all have to re-invent the wheel with gaining access to these tokens—as opposed to providing standard CSS classes and custom properties that everyone can share—then there will end up being more collective CSS, even if core's CSS is pared down to only reflect what's on the page. Utility class CSS tends to be very lean (especially something like the proposed limited toolkit). I would argue that if a few extra kilobytes of CSS can greatly increase the consistency and interoperability of code throughout the entire WordPress ecosystem, that's a good tradeoff.

Also, I think these classes could be a huge boon for block patterns which might also make it harder to detect which styles are needed. A small utility class stylesheet just feels like a small cost to pay for the opportunities it opens!

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.

I think there's some truth to what you're saying here. When an editor chooses a "Large" size, I don't think they're thinking about content portability at all. They're rarely even thinking about how it looks on mobile. However, that lack of foresight shouldn't be a reason to not help users create portable content.

I think what would be "semantic" is not "Large", but "the 4th point on a 7 point scale" (which is larger than the 3rd point and smaller than the 5th). If your saying this proposal won't make theme switching perfect every single time, then I completely agree. But I do think that a standardization like this will take the situation from "not portable" to "mostly portable" which would be a dramatic improvement.

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

As long as those colors can be easily inherited when switching themes, dropping them from this proposal is totally fine as that would mean the need is already met.


@kevin940726 @markhowellsmead I considered proposing data attributes too. I don't have a problem with using attribute selectors in CSS, but the reason I didn't think it made sense is because there are already so many classes. I think it makes sense to continue using classes and extend the existing system. Using data attributes would require disrupting developer habits and probably ending up with a mix of two systems, attributes and classes due to back compat needs requiring that we keep existing classes around.

One strength of this proposal—at least as I see it—is that it tries to follow the existing paths of the block editor as much as possible so that it will be easier to adopt and not feel like a big deviation for everyone involved. If we were building a brand new system from scratch, I think data attributes would need to be strongly considered, but it feels to me like this editor outputs classes and so we should stick with that.


@cbirdsong

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.

Based on your font-weight example and some of @justintadlock's spacing suggestions, it does seem like a system that allows theme authors to omit/combine certain values on these scales would be very helpful. (Although I also think there's a risk of over-engineering here!) The trick is figuring out how to "merge" points on the scale together so that omitted points are rounded to their nearest defined value, maintaining the relationship between user selected values. I think custom property fallbacks could end up being of huge value for doing this, and I built a proof of concept idea of how standardized classes based on optional custom properties could be used for variable scales.

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

I think percentages are probably values that make sense moving from site to site and that feels different from the scales and names in this proposal. I think that is worth creating a new ticket for. I think the same approach might work well for both heights and widths.


@eric-michel

But I do worry that creating such a standard will result in less variety of theme design. With 5 defined colors that offer interoperability with lots of big name plugins, how often will themes go outside of that 5-color dynamic, even if the option is available?

I share your fears and hope that this proposal very much enhances the ability for themes to go beyond the standards. I think it's worth breaking down the separate goals of this proposal which are complementary but also support multiple positive outcomes:


@youknowriad

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.

The goal of this proposal is very much to lean on theme.json as the first and primary means of design customization. However, it also assumes that it will never be sufficient as the only means in all cases. As I mentioned above, I think theme.json should take a carrot approach (rather than stick) and make sure that it provides ease and benefits that CSS doesn't. But it also should not get in the way of extending site styles and functionality. This proposal is all about going beyond theme.json and to do that we need some better semantics and output. When it comes to front-end semantics, that is the literal purpose of HTML and to a less extent CSS classnames, and so they play a critical role in this.

To take a quick step back, I think it's valuable to think about the rule of least power and the way HTML and CSS are still many people's intro to web design. There is a tremendous amount of power that can come from making sure it is easy to customize a site's design (beyond what's possible in the UI) with CSS (and good classnames in HTML). And again, I think theme.json will be _easier_for most people if they know nothing, so this isn't about shifting any responsibility away from there.

In some ways, this feels the same as block themes which are "only HTML" but are then interpreted and edited with elaborate JavaScript. By making site building work well with the fundamental building blocks of websites, I suspect there is a tremendous amount of potential unlocked by those who gain greater access.


@ellenbauer

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.

So glad you liked the proposal :) Your suggestion is definitely along the lines of what I would propose. In my mind, the CSS toolkit of classes should include any single property-value pair that is required to implement a discreet block setting. My examples focused on things like alignment (align-self: center), but I think your border example is another good one.


Finally, I had a few big-picture thoughts based on overall feedback and responses.

Let's keep this as small as possible!

I think it's really important that a system like this remain relatively small so that it's easy and fast to use (making it more attractive for both new and existing themes). It will also be easier to design and agree on standards if it's smaller. For instance, it seems like we could probably just get away with a single space scale that is used for gap, padding, and margin! Less to write and think about for everyone, hooray!

Remember the scope

The goal is to provide interoperable value definitions for block settings (though the values might be used elsewhere), not a theme's full needs. For example, the block editor might only need 5 options for gap sizes, even if a theme needs more spacing options for custom CSS. I have a strong suspicion that the more values there are, the harder it will be for themes to consistently interpret them. That's why it makes sense to have a primary color but probably not a quaternary one.

This shouldn't undermine other features

As @cbirdsong wrote, it's still important for themes to be able to opt-out of supporting a setting like font weight or provide a specific value like 23px for padding in a one-off situation. It's worth emphasizing that this should be supplemental and happily work in tandem with other systems.

eric-michel commented 2 years ago

@mrwweb

Is there a different way that plugins are expected to access theme.json values from a theme for use in their styles? I am not aware of that.

The only way I'm aware of (and have done limited testing with) is wp_get_global_settings and wp_get_global_stylesheet. But this simply introduces the problem of: "Ok, I can iterate through a list of colors in the theme's color palette, but how do I know which one to use for my form plugin's submit button?" Having standardized tokens, of course, fixes this problem, because the plugin author will know they can choose the accent color (whether it's accessed via class name or grabbed out of theme.json via one of the functions above).

One assumption I'm making is that if plugins all have to re-invent the wheel with gaining access to these tokens—as opposed to providing standard CSS classes and custom properties that everyone can share—then there will end up being more collective CSS, even if core's CSS is pared down to only reflect what's on the page. Utility class CSS tends to be very lean (especially something like the proposed limited toolkit). I would argue that if a few extra kilobytes of CSS can greatly increase the consistency and interoperability of code throughout the entire WordPress ecosystem, that's a good tradeoff.

I can't possibly agree with this more. WordPress has always had a styling consistency problem from plugins all bringing their own opinionated stylesheets to the table. If I'm a relative novice and install The Events Calendar and Gravity Forms and a couple other plugins that bring frontend features, I'm going to end up with a mess of different styling across those features of my site.

If plugins can reliably access, for instance, a primary and accent color and a set of expected spacing options, maybe plugin-generated content can start looking more consistent with any given theme. Along with the added benefit of less overall CSS, as you pointed out.

eric-michel commented 2 years ago

@markhowellsmead

Completely agree that we need configurable widths. However, you can add alternative settings using a block filter, if you need e.g. an alternative width when the core setting “wide” is selected.

This is cool, thanks! I've considered looking into block filters for this purpose, but have held off so far in favor of a simple utility class - mainly because of the learning curve but also because I'm somewhat reluctant to add more invasive changes to blocks for a feature that I suspect will probably eventually make its way into Gutenberg proper.

markhowellsmead commented 2 years ago

I've considered looking into block filters for this purpose, but have held off so far in favor of a simple utility class

That’s all this block filter does: add a selection interface to apply a pre-defined class name. All one would have to do (technically) is remove the control and update the content when (if) a parallel setting arrives in core.

eric-michel commented 2 years ago

Cool! I will definitely check it out!

scruffian commented 2 years ago

there shouldn't be anything that can only be styled through theme.json or is made harder "because you should do that with theme.json

We need to be careful with this...

If we use theme CSS to style something that _can_be achieved using theme.json and specifically the Global Styles interface, then if a user tries to edit this setting using the Site Editor the theme CSS might be stronger, and override the Global Styles setting which would:

  1. Make it look like Global Styles doesn't work
  2. Make it impossible for a user to edit their site design

For this reason I think it's crucial that if something can be done with theme.json, it should be.

fabiankaegy commented 2 years ago

@scruffian In an ideal world yes. But since responsive controls and pseudo-selectors currently are not something theme.json allows you to target the only way is in CSS. And we need to be able to target the elements sufficiently in order to override their values based on the states they are in.

There also are several additional context-based settings that currently don't exist in theme.json like being able to change values based on being used within a certain template part, or a custom post type. These are things which theme.json may get at some point in the future. But there also are use cases like for example wanting to override the primary color based on the currently logged-in user just as one arbitrary example.

At some level, there will always be cases that will not be able to be covered by just theme.json and for custom solutions to work at that point there need to be selectors to target these elements.

Yes, that will open it up for themes to missuse these powers and create instances where they override the style engine. But from my perspective, the ecosystem should be trusted so that users don't want to use themes that don't work the way they expect etc. Completely locking out customizations because we don't ever want a theme to override the global styles systems is too prehibative of custom solutions in my opinion.

mrwweb commented 2 years ago

@fabiankaegy nailed it with this:

At some level, there will always be cases that will not be able to be covered by just theme.json and for custom solutions to work at that point there need to be selectors to target these elements.

This is why I'm encouraging us to think about theme.json as a "carrot" that rewards people for using it correctly but doesn't punish people who need to go beyond it or around it for specific reasons. If misusing custom CSS or theme.json causes problems or makes things harder, that's fine and a place where developer education can help over time.

As people continue to point out, theme.json will never be a 100% styling solution and so we need good classes on all elements and classes for all states. (Edit: And there will also be plugins that ship CSS for block styles, variations, or patterns that don't tap into theme.json.) Beyond the examples offered, there will also always be the need for complex combinations of selectors that theme.json can't support, e.g.

@supports ( aspect-ratio: 1/1 ) {
    .wp-block-group.is-row.alignfull:nth-child(2n) + .wp-block-media-text__media:last-child::after {
        /* … */
     }
}

(That example is made up but it's not that far from real things I've written on projects that weren't all that complicated.)

This ties into a discussion on #39181 about classes for block state. Core WP really needs to assume that developers will have a need for more CSS classes because it's impossible to predict every use-case for something. It shouldn't be on theme authors to open Github issues requesting new classes every time a new feature or element is added. When you power 40% of the internet, you can count on developers findings lots of awesome, creative uses for the tools you apply.

WordPress's hook system is just like the need for CSS classes here. You can completely break WordPress by misusing almost any hook, but that doesn't mean they're a bad idea. In fact, just the opposite. The flexibility and freedom that hooks provide to adjust WordPress's behavior is one of the defining aspects of WP's success!

We should all think back to CSS Zen garden on this point. Nobody could predict all the amazing designs that would happen on that site, but by providing good, HTML with sufficient classes to hook onto, frontend developers do amazing things.

glendaviesnz commented 2 years ago

@mrwweb, the intention is to keep that issue clearly focused on the semantic classnames on the block wrapper in order to minimise the scope and make some progress hopefully.

From the discussions here it didn't seem to me that there was a clear consensus on the need to enforce the addition of strictly named class names on every element in a block, so mixing this into the same issue would likely slow down progress I think.

Thanks, have added a note about this to the issue - do you think it will be a requirement of theme authors to enable/disable both options independently, eg. allow users to select either presets, or, absolute, or both?

@cbirdsong are you happy to do what is suggested here and close your issue and summarise and reference it on the new one? or we could always do the reverse and close mine and reference your one?

cbirdsong commented 2 years ago

I'll close mine and summarize it in a new reply to yours.

mrwweb commented 2 years ago

I want to quote @andronocean at length from their comment on #36135 because it's extremely relevant to this discussion (original emphasis):

From other issues I've read, one of Gutenberg's main rules is that user-applied styles set in the UI should always take precedence. I entirely agree with that.

In practice, however, that is getting muddled with the need for Gutenberg to generate default, global styles based on theme.json. These styles and Gutenberg's own built-in helper styles (like the .alignleft ones) are treated the same as user styles. They're merged together and dumped out in inline stylesheets.

I feel like Gutenberg's style system would do well to separate these things into multiple "layers":

  1. Built-in helpers
  2. Styles from theme.json, color schemes, etc.
  3. Theme-enqueued stylesheets — Note, Gutenberg has NO knowledge what these may contain, so it cannot make assumptions._
  4. User styles set in the UI.

Happily CSS already gives us the tools for this: specificity and the cascade order. The theme styling process would be made far easier and more reliable if Gutenberg kept to a "contract" of sorts for its styles:

  • CSS rules for layers 1 and 2 will never exceed a particular specificity threshold
  • themes can enqueue stylesheets in the after layers 1 and 2 (they can enqueue earlier too, if needed). User styles are printed after all theme/plugin styles.

For that specificity threshold, I'd like 0-1-0, maybe 0-1-X. I could live with 0-2-0 for limited, documented situations where nothing else will work. Though now that browser support for :where() is getting good those are probably slim indeed.

This is a great, concrete proposal that is perfect for illustrating the benefits of CSS utility classes for both core block setting needs (e.g. center alignment, dashed border) and theme.json "tokens" (e.g. large gap, primary color, heading font family).

eric-michel commented 2 years ago

Regarding CSS specificity, I wonder if it does make sense to start looking at CSS layers, as @adamziel suggested. Current versions of all 4 major browsers now support it, and by the time anything related to this project is ready to merge, it's likely layers will enjoy broad support for most users.

markhowellsmead commented 2 years ago

I love the sound of CSS layers, but the tech. is only 20 or so days old. It will be a while until this is supported in 90%+ of active browsers and core must maintain support for the large majority of visitors.

If CSS layers were to be added as a basis on which core CSS works, working around it for older browsers will likely either create a huge amount of work for theme developers - especially those of us building custom themes for clients - or will encourage us to simply abandon core CSS. This seems like a big step backwards, when the core CSS could (can) be re-organised using current methods and made more simple without the need for such progressive CSS.

The use of CSS layers is a great step for CSS, but the issues we all face at the moment to work around unwieldy and overly-specific CSS rules (0-2-0 and higher) won't be improved by using “the next big thing”. The current CSS needs to be improved and simplified first, before embracing future enhancements.

cbirdsong commented 2 years ago

If CSS layers were to be added as a basis on which core CSS works, working around it for older browsers will likely either create a huge amount of work for theme developers - especially those of us building custom themes for clients - or will encourage us to simply abandon core CSS. This seems like a big step backwards, when the core CSS could (can) be re-organized using current methods and made more simple without the need for such progressive CSS.

100% agree. If the editor is going to ship important structural CSS it needs to be conservative, and @layer will be bleeding edge for many years.

andronocean commented 2 years ago

Hey y'all, and thanks @mrwweb for mentioning me on this. There's so much in this thread that I haven't read it all in detail yet, so apologies if I'm missing context or repeat something.

I also agree that @layer is far too experimental to use in production. The "layers" I suggested in my comment (quoted above) would be a sequencing mechanism or API inside of WP, to control the order in which styles from Core, themes, users, etc get added, for a more predictable and extendable cascade. (We could call the mechanism something different, like "stages", if we wanted to.)

From what I've read so far here, I'm even more convinced such a system would be useful. As more styling functionality gets added to theme.json, the more likely conflicts with theme CSS become. In the current theme I'm writing, I've found myself removing settings and styles from theme.json and from blocks, and replacing them with my own utility classes just because I needed to tweak things responsively.

This problem would exist even if all classnames were standardized, all possible block states had targetable classes, and the Style Engine was fully operational. (It may actually get worse — that level of standardization would inevitably encourage everyone to use those classes in their CSS — leading to even more specificity conflicts.)

But if we had a contract for specificity and a layering/staging system, the proposals in this thread would become more useful, possibly easier to implement. It would all be complementary — full block state coverage with classes would help keep specificity down for default rules from Core.

Perhaps most importantly, core functionality could be added and tweaked without breaking existing themes' designs*, while still giving users full customization control in the editor. The same would go for plugins that extend particular block designs, or add new ones.

*No breakage within reason. If a theme's CSS targets h1 and expects it to always work, that's unlikely. Themes would still need to make sure their specificity was high enough, but that level would be well-defined.

We haven't needed anything like this before now. With Classic themes, almost all the important CSS came from the theme or the user (styles from plugins were basically a convenience proxy for the user). Hook priority and deps for wp_enqueue_style() were enough. And as long as blocks were confined to the_content(), there wasn't a large interface where conflicts could arise. Now the interface is all markup on every page. That gives great power, but also makes for much more uncertainty; the CSSOM a theme author is integrating with today is not necessarily what will exist tomorrow. A layering system and specificity contract would restore that confidence.

(Footnote: we'd need a system like this in order to use @layer in the first place, should that be worthwhile someday. I like it theoretically.)

anarieldesign commented 2 years ago

I couldn't agree more @mrwweb 👌. As a theme author this would be sooo useful. Thank u soo much for writing this amazing blog post 👌. And would be useful to have css classes instead of inline styles for some blocks.

SamuelMiller commented 2 years ago

How about incorporating an already existing universal nomenclature Bootstrap-5 framework, and its presets, into WordPress There is a recently released WordPress plugin, "All Bootsrap Blooks" by Areoi (with which I am not affiliated) which does this to a certain degree. The developer(s) will apparently release a bootstrap theme, their own theme.json to go with the plugin. I am curious to see the nomenclature used in their theme.json. Perhaps, developers of WordPress may want to collaborate with the developer of "All Bootstrap Blocks" to Bootstrap-ize WordPress by default?