-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
General CSS Concepts: Declaring style
vs. describing state
#38694
Comments
Thanks for kicking off this discussion. I've added some labels to ensure folks can find it more readily. I might also bring this to the next Core Editor meeting (you can see agendas here). To cross connect what feels related too, here's an issue on standardizing a way to modify hover/focus/active states #27075 |
Correct! I believe they influence each other so wanted to be sure there was some cross connection. |
#36525 is another related ticket, where the Navigation block saw the removal of a descriptive |
I find the There are exceptions to this though, for example some block attributes don't map cleanly to one CSS rule. In these cases a. |
That is a nice thing to want, but it simply does not reflect the reality how WordPress is currently used and what users (end users and developers) expect. As I said in #38719: Removing explicit classnames that describe state in favor of implicit, random classnames that describe style would effectively make any Gutenberg frontend a walled garden, which is the antithesis to openness. At least from our point of view this has to be avoided at all costs. |
The problem is this contract is not clear and far from complete as we enter the 6th year of Gutenberg development. Gutenberg does not yet enable precise and predictable content layout for even quite simple designs, and themes conveniently continue to do the heavy lifting in many regards.
This may be fine when a theme has originated in the editor and is naturally contained by the UI exposed by Gutenberg, not so much when bringing a 3rd-party design to Gutenberg. Unless designers embrace designing with Gutenberg—unlikely for the majority considering the far more expressive visual layout tools that are currently available—there will always be exceptions that Gutenberg cannot reasonably anticipate but needs to accommodate. With Gutenberg assuming responsibility for the shape of component markup it becomes especially important to bake in attributes that can help theme developers target anything Gutenberg hasn't anticipated. |
I'm working on a blog post to hopefully provide greater detail, but I want to weigh in now to hopefully keep this conversation moving. I'm hitting publish a little sooner than I normally would. I'm happy to clarify futher It appears that the vision of Gutenberg involves no theme-authored CSS and I don't think that's possibleFor a long time, I have not had a clear understanding of the final vision for how WordPress styling will be done. Both on this issue and others, it sounds like core developers envision no theme-provided CSS at all.
If that were possible, then the direction of using inline and autogenerated styles makes lots of sense. However, I just do not see how that is possible. I can't see how
I can also envision lots of other scenarios where themes need access to block's state on the front-end that I can't imagine theme.json supporting. For example, creating a set of scroll-triggered animations of the Media & Text block that are dependent on the position of the media and the vertical alignment of the content. The changes in 5.9 have already broken sites for me by removing some of these classes like the ones on the social icons block. Update (14 Feb 2022): I was directed to @mtias's comment on #35469 by @luisherranz today which implies that the intended CSS responsibility is intended to be more like 80/20. Not only does that feel right, but it makes it extra clear that themes and plugins need consistent markup and style expectations to customize core markup and styles. Big picture: @luisherranz was super smart to ask the question on that thread the way they did and it answered a huge question I know I and many other theme builders have had for a long time. The answer also seems counter to a lot of other comments and decisions I see happening right now when it comes to markup and CSS. It feels like a period of broad community discussion and feedback is needed to make sure that the direction of markup and styles in core is lean, fast, accessible, portable between themes, and customizable outside of the UI. Theme Lockin vs. Theme FlexibilityIf the plan is that WordPress outputs all CSS as inline styles and style blocks, then it would become very hard to move content to a new theme because so much of the styling is tied to specific block style values. I don't agree with the comment above:
The beauty of switching themes is that the content stays the same but the design (aka "theme"!) is different. People specifically switch themes so that their sites look different. If there are standardized classes and design tokens (more on that in a moment), then themes can faithfully reinterpret user choices, letting them shift their content cohesively from one design system to another. With recent talk of creating an interoperable block standard across platforms, it's even more critical that the state of blocks is represented in the markup and not the styles. What A Better Future Could Look LikeHere's a rough outline of a better path forward. Happily, this builds a ton on the groundwork already by WordPress! Design Tokens: I think Compontent Framework: Blocks are already components, and yet they don't feel much like they're treated as standardized HTML front-end components. A while ago, I tried to document all possible selectors for blocks so that themes could have a set starting point for theming. I know now that many of these selectors are incorrect and out-dated. These classes are GREAT. They should be carefully considered, document, and standardized. What this OP is asking for—and what I thoroughly believe the future of the block editor and FSE should be—is for a set of front-end components with consistent markup and CSS classes that are given baseline values by the design tokens in theme.json. Inline Styles for one-off values: There will still be moments where user-customized values need to be inlined. Column width percentages are a great example. It's fine for these to be inline styles. They are one-off decisions made by editors that should be treated as one-off styles, which is exactly the purpose of the style attribute or style element. When put together, you get something that is close to what currently exists but is significantly more standardized, documented, and leaner due to better shared styles. I'd love to see BEM-style classes for each block with utility classes tied to theme.json styles, much as is described in the CSS-Tricks post: "Building a Scalable CSS Architecture With BEM and Utility Classes" The Advantages of a Front-end Framework +
|
This exactly what I thought |
I don't have much to add either at this moment, but I started a couple of related discussions a few months ago to try to understand what are the new theme and block responsibilities and what does the block-theme contract look like: |
I posted this over on #35376 but figured this would be good to repost on - since I discovered it from that issue. I've been tinkering with this quite a lot and it certainly gets pretty complex fast. (Note, none of this is meant as critical, just observation and trials-and-tribulations from the trenches...Gutenberg definitely is like it was once described as "trying to replace a jet engine while the plane is in flight." Part of the issue is that while most plugin or theme developers won't care as deeply to what's happening under the hood with backend processes (except to adapt to how things are done or hook into something, etc), CSS ends up being a whole different ballpark because of the impact on the visual front end - and as someone who runs an agency, CSS is almost always the area where the most time is spent/lost. Worse, it's also by far the most opinionated (or at least vocally opinionated) because it's the area someone's most likely to notice. For example - FSE radically sped up our dev process (been building them in production since 5.8) for the average small business site in terms of setup, migrating features from one to the next and so on. That said it's a chronic fight with CSS on a scale not seen before in WP - a strength of a classic theme is that, well, you can just dequeue everything and load up whatever you want. That simply isn't possible with blocks - or more specifically - with theme.json (which is something that's great ), since that has to be a dynamically generated stylesheet. This means that opinionated styling is the default, because, well, it has to be in this iteration. The ProblemLet's evaluate a seemingly simple problem. CSS works best when it's leveraged to it's full inheritance properties - setting smart, global defaults that are only overridden for specific, actual, exceptions to the global standard. Simple Problem 1: On mobile, a paragraph or heading block on it's own within a container without a background by default ends up flush against the side of the device screen. Easy Solution: Make the the global default box-sizing: border-box & direct child of the main content box section default to content-box model.
Now, the content will align perfectly on a screen size larger than contentSize or wideSize and on fullwidth, as originally intended, but on smaller screen sizes won't be flush against the size of the device. Here's the problem: wp-block-group, column, etc all explicitly state to be border-box...so now I've got to override those styles by either dequeuing them and loading my own styles or overriding their CSS, and anything else that explicitly says border-box, when it wasn't necessary with a global default like box-sizing: border-box. But hey, that's easy enough to override - we can override the block styles, so we'll just dequeue them and enqueue what we want after reading through every style and eliminating what we don't need and re-enqueuing a new style. It's a PITA the first time, but once we got it, we got it mostly, and then have to keep up with more internal changes with core...or we could completely forego core blocks. But then, it very quickly gets more and more complex.But, let's go to the next problem - okay, my page load speeds are pretty awesome with FSE, so I don't really need to worry too much about dynamic .wp-container-uuid, even though it's a pain to read through to diagnose CSS, it's functional. But, now I want to make it so my theme doesn't have to account for different reading modes by writing new CSS - which is solved with single line logical properties instead of margin-right / left etc, we can use margin-inline-start / margin-inline-end etc and the layout will automatically change based on the user's reading mode preference. Awesome! I can keep the integrity of my design, be internationalized by default, and not write anymore code. Now we have to rewrite the Layout Support part of Block Support API to fix that...Except now I can't because Layout Support ties me to being margin-right / margin-left...and to overwrite it, I have to rewrite the whole Layout Support section of the Block Support API. Now I've got to maintain a fix that core could override or change at any time, or I'll have to adjust to account for new features. Then, there's the whole tied into global core colors and such, which is livable, but annoying to read through during development and well, would be great if it could just be opt-out gone - the reason it's global and not overridable is for block patterns, but it feels like a relatively simple version of an if loop could just be attached to the render_block hook to pull the global style into the common global CSS style sheet if the property is used. Plus, then that brings the thought - so great, we made global colors and gradients non-opt out for custom props, so, why not make a custom prop for contentSize and wideSize and simply change that value on a one off tag (or call via an id property) when a user input is put in place? Then, on top of that, half of the Block Support API registers values from theme.json one way (e.g. apply) and the other half uses render - presumably because a custom contentSize / wideSize can't be inherited using the owl selector if it's an inline style. Effectively, four different sources of truth for how the CSS is generated and where it comes from.So we've now got four different ways CSS is rendered without getting into anything super complicated - there's the core styles that get output inevitably through script-loader, inline styles via Layout Support and Duotone support, the individual blocks themselves, and whatever other CSS is left-over from older enqueues (e.g. wp-emoji - although, being dequeueable that's not such a big deal). This isn't even including something like - colors - in the block editor which all default to HEX overrides - and not in a way that enables opacity (e.g. via 8 digit hex). But because there's no singular area where CSS is resolved, the easy solution of simply having a JS or PHP script that powers the color wheel and converts the values to whatever the custom property set it as or let the end user set whatever color code they want to use and have them render that way - which would enable things like opacity to work out of the box (e.g. avoiding the cover block opacity thing that has to use a filter to override the color) - and be easy enough to say, hey, RGB to RGBA, Hex to 8 digit hex, HSL to HSLA, etc. If I remember right, I think the customizer even has something like that for it's color wheel! CSS is complex - what's needed is a CSS API.A centralized CSS API that parses CSS from various sources and generates it in one spot would simplify the development process of further features, streamline how the CSS is created and where it's output, and enable a singular area for hooks to be generated so developers could hook into what they want and manipulate values or replace them as they need to without messing with the core interface. Here's the solution I've been building independently for my agency within a plugin for our internal use so far.
We save different copies of the parsed data in a custom post type - so that if a user wanted to change what the default output is, say change a property from margin-right to margin-inline-end, they could simply change one value and it goes out - though this probably isn't something core would really need to care about - but I think giving a plugin or theme author the ability to hook into this mechanism would be widely applauded. In our case we're just using the CPT as a data store that can be easily edited / referenced once the CSS is already parsed. CSS is complex enough that I feel that a class / API that powers parsing and creating declaration blocks and assigning them to stylesheets would radically simplify the development of the block editor - and even provide a framework for blocks themselves to radically simplify the amount of CSS they have to render for options, while giving plugin & theme authors a way to change those values if they so desired, without taking away the power of choice from the user. The bottom line is - no theme or plugin author should have to completely overwrite a core API to change how a couple of lines of CSS are generated. |
So many excellent ideas in this thread 👍
This is something we could and probably should look at to ensure margins and padding behave across writing modes. I've started looking at alternate writing modes. If it gets any traction switching to logical properties might have to happen anyway. Anyhoo, here's a draft PR that experiments with switching the layout margins to CSS logical properties: 😄
It'd be fab to capture some of these ideas over at Tracking: Add a Style Engine to manage rendering block styles. The scope is fairly narrow so far, but it's still early days, but ideas, feedback and discussion at this point would be highly valuable. Particularly the experiences surrounding the CSS API implementation outlined above. 🍺 Cheers! |
@ramonjd Even if it never "catches" on, it's a smart, sensible default, we've been using it without issue for almost a year now - for one, the semantics follow flex & grid semantics, which you pretty much have to be accustomed with in modern CSS anyway, RE: CSS APII could probably convert the API I have right now to a more WordPress coding standard style if that'll help move the needle in a PR, since I've already got the bulk of the work hammered out in a scenario that works on top of Gutenberg / WP Core. I just tend to write smaller classes that use interfaces for dependencies and are a bit more component-sized to be easier to hot swap in my own coding. A more finely tuned core-integrated version would de-couple the CSS logic from WP_Theme_JSON and related classes (which I haven't gone to that length, since overall the Theme JSON API is great, and decoupling it altogether in a 3rd party plugin requires a massive amount of re-write of a lot of related classes. But, under the hood, I rewrote the old PEAR PHP package for parsing CSS from a file / html / etc and paired it with a 'fromAPI' version that took the data from wp_get_global_stylesheet() etc. There might be a cleaner solution than using a CPT as a data store, but I figured to follow the pattern from similar use cases (like block templates, navigation block, etc). |
Major agreement with @mrwweb and @Luehrsen. I also want to chime in on this bit, which gets at a pain point for me:
If this is the goal it is massively undermined anytime the editor outputs inline style values, especially when it's the only option to adjust something. To avoid de facto lock-in, there needs to be a level of abstraction between what the user can edit and what comes out on the front end, and Gutenberg already found a decent solution: presets. Themes registering a range of font sizes and color/gradient values has already proven to be a reasonably1 successful balance, providing a structure for a theme to use to ensure design consistency while reducing cognitive overhead for content editors. Three existing problems that could be solved by presets:Spacing, Margins and PaddingTheme designs inherently have different default spacing values, and consequently, any custom spacing value that looks good on one theme could be cramped on another. A user might increase the padding on a column block or decrease the spacing on a buttons block based on the default in their theme, but once those choices are moved into the next theme they're likely no longer going to fit. Similarly, block patterns might include a hard-coded padding size that doesn't fit in with the theme for the same reason. Additionally, fully custom values are fixed and can't change for different screen sizes. The user almost certainly doesn't want a block's padding to be 75px on a 375px wide phone display, but they probably won't think about that when typing "75px" and seeing it look pretty good on their desktop display. A standardized set of presets ("small", "medium", "large", etc) would allow a user to tweak these values while sidestepping all of these issues. I previously proposed this solution for spacing in an issue last year: Font Weights5.9 added the "Appearance" (font weight) setting, which has options for every weight going from 100 to 900 and outputs that value as an inline Even discounting that, the lack of abstraction inherently poses a problem since one theme's "bold" weight might be A standard abstracted set of font weights like "light", "normal", "semibold", "bold", etc. would allow content to smoothly transition between themes and typefaces, as well as allowing theme authors to add more options without filling the user's content with hard-coded font-weight values. Column Sizing
I have been using block filters to accomplish this on my recent themes, and it makes it much easier to manage the layout of columns/media & text blocks in various contexts while still allowing users to set a custom width. However, presets would be greatly preferable to requiring users to enter arbitrary percentages. A grid system-style set of width presets (50%, 33%, 25%, 20%, 16%) would cover most use cases and could also include intrinsic options like "max-content" or "auto", which is a much better way to think about layout on the web than fixed percentages. In my opinion, any option that results in an inline style value being injected into the page should have a set of presets presented to the user before allowing them to set a custom value, just like font sizes and colors. Themes should also be able to easily opt out of using any custom values even if they already exist in the content, which is not currently possible with custom font sizes and colors. I would love to know why the Gutenberg team has gone the opposite direction and moved away from using presets in favor of asking users to enter arbitrary values using units they might not understand and requiring theme developers to deal with the resulting inline styles, especially considering the many issues complaining about it:
Footnotes
|
I just published a more detailed proposal for trying to address this issue while meeting some other related needs. #38998 is the corresponding issue for the blog post. |
I feel we have a conflict of concepts in the project at the moment. I am not sure in which direction we are going (in terms of this specific concept) and I think a clear definition of what we want to achieve would be beneficial in understanding the rest of the components and set clear(er) expectations.
Definitions
Before we talk about the conflict of concepts themselves I want to quickly describe (my understanding of) the meaning of the concepts in conflict.
Describing State: Following this concept we want to describe the current state a component, or block, is currently in. A state would describe if an element has a background with the
has-background
class, or which font size it has withhas-font-size-regular
. We use these states all over the project and we can easily spot those descriptor-classes with thehas-
oris-
prefixes.Declaring Style: Sometimes we add some specific style declarations to elements to reflect a state. One example would be the width of columns in the
core/columns
block. If we set a column to 12% width, aflex-basis: 12%
style declaration is added explicitly, as a general description of state (e.g. withhas-width-12percent
) would lead to a lot of redundancy and duplication. (I'm looking at you, Tailwind!)Why this matters
As theme developers we sometimes like or need some leeway in terms of how to interpret a certain state of a block or element. For example
has-font-size-regular
imposes no opinionated value of whatregular
means. We have some default settings set by the block editor, but as a theme we are free to interpret whatregular
means and overwrite the defaults.On the other hand, setting a width on a column declares a very strict
flex-basis: 12%
, which locks us as theme developers in a corner. We have to use flex for columns, even it we wanted to use something else (like css grid).My proposal
For us as themers interpreting a state gives us a lot more freedoms to interpret the assumptions compared to working with declared styles. So I would love the project to explore where a style declaration could also be a state description.
One important aspect of modern CSS is the fact, that we do not only have classes to describe state, but also CSS variables. So for example setting
--wp--has--width: 12%
on acore/column
block would still allow the project to have explicitly set defaults, but at the same time allow themers some more freedoms of choice with how we interpret the state.The ticket #38677 is another example of where a descriptive state would be helpful and a declarative style is applied.
So I would love to add "Describing state over declaring style" to the set of values for this project. I think it has value to encourage contributors to use state instate of styles when refactoring or newly implementing (frontend) elements.
Thank you!
The text was updated successfully, but these errors were encountered: