Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Global styles – per-block custom css #44412

Closed
jameskoster opened this issue Sep 23, 2022 · 29 comments · Fixed by #46571
Closed

Global styles – per-block custom css #44412

jameskoster opened this issue Sep 23, 2022 · 29 comments · Fixed by #46571
Assignees
Labels
Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json Needs Dev Ready for, and needs developer efforts [Type] Enhancement A suggestion for improvement.

Comments

@jameskoster
Copy link
Contributor

jameskoster commented Sep 23, 2022

When editing the styles for a specific block, it might be nice to offer an input for custom css. This way folks don't have to worry about finding / using the best selector.

Mockup: a "Custom CSS" button at the bottom of the Advanced section of a Group block, which slides the inspector to the right just as the content-only editing system, and Global Styles, does:

Group custom CSS

This could align well with how it could look for Global Styles:

Global styles custom CSS


Issue updated Oct 25. Related to #30142

@jameskoster jameskoster added [Type] Enhancement A suggestion for improvement. Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json labels Sep 23, 2022
@mtias
Copy link
Member

mtias commented Oct 7, 2022

Some prior art: #25413

@mtias mtias added the Needs Design Needs design efforts. label Oct 7, 2022
@mtias mtias mentioned this issue Oct 7, 2022
57 tasks
@jasmussen

This comment was marked as resolved.

@jasmussen jasmussen added Needs Design Feedback Needs general design feedback. and removed Needs Design Needs design efforts. labels Oct 25, 2022
@jameskoster
Copy link
Contributor Author

I like the implementation – a dedicated panel makes sense and provides adequate space. The connection to Global Styles is nice too.

But that Advanced panel is starting to look a little chaotic. Obviously it's separate, but I wonder if we should give it the Design Tools treatment?

@jasmussen
Copy link
Contributor

But that Advanced panel is starting to look a little chaotic. Obviously it's separate, but I wonder if we should give it the Design Tools treatment?

Sounds good to me, including keeping that separate! 🚀

Alright, I'll update issues.

@jasmussen jasmussen added Needs Dev Ready for, and needs developer efforts and removed Needs Design Feedback Needs general design feedback. labels Oct 25, 2022
@jasmussen
Copy link
Contributor

Added "Needs Dev" and updated the main issue. Should be good to work on!

@aristath
Copy link
Member

aristath commented Nov 9, 2022

We'd like to start working on this issue, but there's a decision that needs to be made before we start.
The main problem we saw when discussing this, is how to handle the selectors in our custom-CSS field.

Assuming we want the final CSS to be like this:

.wp-block-search {
  background: #222;
  border: 0;
}

we have a few ways to accomplish that:

Allow users to arbitrarily add custom CSS

The problem with this approach is that each snippet of custom-CSS in this case is not actually "per-block", but global. I can easily add some CSS in the search block, and target an image block - which is something we should definitely avoid.

Use a hard-coded selector per-block

We could define in block.json that the selector for the search block is .wp-block-search. In this case, the per-block "custom-CSS" field would accept something like this:

background: #222;
border: 0;

Internally we can then build the CSS from that, by doing $selector { $customCSS }.
That's simple and can work. However, if we go with something like that, it's impossible to target pseudo-elements and states like :hover, :focus, :after and so on. So while the example CSS above can be written by omitting the selector, we wouldn't be able to write something like this:

.wp-block-search {
  opacity: 0.8;
}
.wp-block-search:hover,
.wp-block-search:focus {
  opacity: 1;
}

So we need to come up with a different solution.
One option would be to use a placeholder like el (short for element). In this case, we can just replace .wp-block-search with el when writing the CSS, and internally we make the conversion.
Another option would be to omit the selector completely, so the user would write something like this:

{  opacity: 0.8; }
:hover,:focus { opacity: 1; }

Or, something like this:

opacity: 0.8;
&:hover: opacity: 1;
&:focus: opacity: 1;

I personally like that last one as it would provide a lot of flexibility, but in this case users wouldn't be able to just copy-paste a snippet they found online and expect it to work, not to mention the complaints we'll get like "After reinventing HTML, now you want to reinvent CSS"

We need to decide how we want the CSS to be formatted - because it definitely should not be 100% freeform, it would defeat the "per-block" purpose.

@jasmussen
Copy link
Contributor

Nice, Ari!

The example CSS in the mockup was perhaps a bit misleading, I copied it from a template. But I like @youknowriad's solution from #25413, which was to use :self:

:self {
    background: red;
}

@fabiankaegy
Copy link
Member

To further expand on the very insightful note from @aristath, how should the custom css that is input in the settings of a block only apply to that particular instance of the block or all blocks of that type that are loaded on the page?

@aristath
Copy link
Member

aristath commented Nov 9, 2022

Nice, Ari!

The example CSS in the mockup was perhaps a bit misleading, I copied it from a template. But I like @youknowriad's solution from #25413, which was to use :self:

:self {
    background: red;
}

Ah I had forgotten about that one! Yes, I'll agree with that proposal. Using :self as a selector placeholder would simplify a lot of things without being too restrictive 👍

So... Should we start implementing this with the assumption that we'll use a placeholder like :self for the block selector (as opposed to all the other alternatives mentioned in the comment above)?

@jasmussen
Copy link
Contributor

Quick and dirty mockup, with the :self wrapper locked in place:

Group custom CSS

What do you think?

@jameskoster
Copy link
Contributor Author

Just to play devils advocate, do we definitely want to enable pseudo-element targeting here? If so where do we draw the line, should we also support things like :first-letter, :before, :after?

Personally I would err towards being a little more restrictive here. We don't want people writing the css for their entire site in one single block 🙈

For the sake of argument, we might do something like:

Screenshot 2022-11-09 at 11 28 11

And point folks to the custom css feature within global styles itself for more comprehensive styling. Just an idea.

@jasmussen
Copy link
Contributor

:self would lock you into CSS for just this group and its descendants, and would useful, I'm sure, for patterns.

I like to think that hover/focus styles and their handling through theme.json can be evolved a bit further with a UI before we start to optimize for their CSS input.

@jameskoster
Copy link
Contributor Author

Ah yes, if :self is locked and not a placeholder that sounds fine.

@aristath
Copy link
Member

aristath commented Nov 9, 2022

:self would lock you into CSS for just this group and its descendants, and would useful, I'm sure, for patterns.

+1

@jameskoster I had a discussion with @carolinan earlier today about that exact same thing (using a dropdown to allow users to enter styles for specific pseudo-elements)... The problem we found is that this would be too restrictive. :hover, :focus, :after etc are just a small subset of the things that someone will want/need to target!
A lot of blocks have children, or a user may want to target things using :not() etc.
For example, using :self, a user would be able to write things like this:

:self:not(.some-random-class) {
  color: #fff;
}
:self > caption {
  background: #000;
  opacity: 0.8;
}
:self:hover > caption {
  opacity: 1;
}

@youknowriad
Copy link
Contributor

We could also embrace the fact that CSS nesting and allow something like:

color: red;

&:not(.some-random-class) {
  color: #fff;
}
& > caption {
  background: #000;
  opacity: 0.8;
}
&:hover > caption {
  opacity: 1;
}

some child {

}

I'm using SASS syntax here but nesting is coming to CSS too, so we can align with CSS syntax too.

@aristath
Copy link
Member

aristath commented Nov 9, 2022

We could also embrace the fact that CSS nesting

We could do that, yes... And it would be relatively easy to handle the & things in PHP & JS to build the proper selectors. 👍

@mtias
Copy link
Member

mtias commented Nov 10, 2022

I cannot find where I mentioned this before, but we could also consider :block instead of :self for extra clarity.

We definitely want to make this very flexible and open ended, the only enforcing is the block class name being dynamically generated and the fact we only output this CSS if the block is used on a given request.

@carolinan
Copy link
Contributor

carolinan commented Nov 10, 2022

It is difficult to estimate how the custom CSS fields will be used and what the different users types beginner, developer and designer expects without user testing.

Yes it will be used by people who understand SASS, nesting, :self (and know enough that :block is something custom), but wont the majority just use their search engine to find a code snippet that they will copy paste into this box, and they expect that to work without having to know how it works.

@jasmussen
Copy link
Contributor

If :block or :self are locked in place, code snippets would presumably work, even if perhaps they'd require removing the element. But it seems important specifically for the per-group CSS styles to have some built-in scoping, lest you install a pattern from the directory and it bleeds into all surrounding blocks in an unfortunate way.

@carolinan
Copy link
Contributor

Yes there needs to be built in scoping, my concern is how it is presented to the user.
Can we limit the first implementation to simple blocks first and consider patterns later?

@jasmussen
Copy link
Contributor

I was thinking this design for the locked CSS code, and by patterns I'm not expecting any separate work at all, simply that if you create a pattern that includes a group with CSS, the pattern will have CSS.

@carolinan
Copy link
Contributor

The field in the customizer has the following text:

Add your own CSS code here to customize the appearance and layout of your site. Learn more about CSS

When using a keyboard to navigate:
In the editing area, the Tab key enters a tab character.
To move away from this area, press the Esc key followed by the Tab key.
Screen reader users: when in forms mode, you may need to press the Esc key twice.
The edit field automatically highlights code syntax. You can disable this in your user profile

@carolinan
Copy link
Contributor

carolinan commented Nov 11, 2022

I will try again. I don't think that displaying :self, block or similar in the input filed will be helpful, unless you would consider having an advanced mode.

This is what I think will happen:

  1. Why is there already text in the box?
  2. Where is it from?, because I did not add it.
  3. Why can't I remove it?
  4. What does it do?
  5. What do I do?

Having a code snippet already placed would be more confusing than helpful.

An advanced mode could have the code, the scoping, visible, as well as have the syntax highlighter by default.

@noisysocks
Copy link
Member

@Mamaduka: I see you're working on #30142. Do you think it also makes sense to take this one? They seem closely related 🙂

@wingmatt
Copy link

It's exciting to see this option being explored, and I see a lot of great ideas have been shared already!

I love the idea of having a variable like :self (or :block) to reference the dynamically-generated block ID for easy block-scoping specificity. This is what Elementor does with its Custom CSS section, and it's perfect for fulfilling styling edge cases that no-code tools can't cover:

Elementor Custom CSS widget. Includes helper text explaining to use Selector to target the widget being edited

With that said, I'd like to gently challenge the assumption that this custom CSS should always be wrapped in a :self selector.

Consider blocks that render child elements, such as WooCommerce's Hand-Picked Products block. In these cases, locking the custom CSS so it can only affect the block's container element via :self would remove most of the utility this section would otherwise provide.

I recognize that this section would be a tool for folks of all CSS skill levels to use, and how that requirement introduces some tricky UX challenges. If the custom CSS could be locked to :self by default, but with some sort of option to unlock it without cluttering the interface, that would be ideal in my opinion!

@oandregal
Copy link
Member

Looking at the options, if we actually want this CSS to be scoped to the block, I'd favor this approach (not using :self or :block but making sure everything is nested within the own block selector derived from block.json).

The rationale is that if we use :self or :block, how do we make sure this does not happen?

:block { /* some block styles go here */ }

.wp-element-link { /* CSS unrelated to the actual block */ }

@aristath
Copy link
Member

aristath commented Dec 1, 2022

Looking at the options, if we actually want this CSS to be scoped to the block, I'd favor #44412 (comment) (not using :self or :block but making sure everything is nested within the own block selector derived from block.json).

I tend to agree with that assessment... The danger we run if CSS is unscoped, is that we're basically introducing 70+ custom-css fields that can be applied globally 😅

@oandregal
Copy link
Member

Shared #30142 (comment) a potential first step for both this issue and the other one.

@mtias
Copy link
Member

mtias commented Dec 1, 2022

I also lean towards enforcing the scope selector initially and remove possible footguns. Tracing orphaned selectors across a large collection of blocks is not going to be fun. With native CSS nesting hopefully coming soon we should be able to design for this anyways:

:block {
  /* some block styles go here */
  .wp-element-link {
    /* CSS unrelated to the actual block */ }
  }
}

Which would handle most needs in more complex blocks. Alternatively we could allow :block .wp-element-link {}.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json Needs Dev Ready for, and needs developer efforts [Type] Enhancement A suggestion for improvement.
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

10 participants