w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.46k stars 657 forks source link

Feature to define an arbitrary number of pseudo-elements #6169

Open GCSBOSS opened 3 years ago

GCSBOSS commented 3 years ago

Disclaimer

I imagined this would have been proposed three gazillion times by now. Though I haven't found anything meaningful on the internet. Therefore here we go.

Proposal

I propose a language feature to allow authors to define an arbitrary number of pseudo-elements similar to ::before and ::after.

Benefits

I'm surely missing some good benefits here.

Problems

I am not really sure. Certainly, you people have great experience in spotting CSS proposal loopholes. Thank you!

Syntax

Here goes the first syntax that comes to mind.

p::leading(1) {
    /* First pseudo-element */
}

p::leading(2) {
    /* Second pseudo-element */
}

/* ... So on */

p::trailing(1) {
    /* First pseudo-element after last child */
}

p::trailing(2) {
    /* Second pseudo element after last child */
}

/* ... So on */

Alternative Syntax

Maybe we can use names and define order in the properties.

p::pseudo(foo){
    pseudo-order: 1; /* first pseudo element afrer children */
}

p::pseudo(bar){
    pseudo-order: -4 /* before children and before -3, -2 and -1 */ ;
}

Maybe we can just ignore ordering altogether and focus on using existing z-index and order behavior where applicable.

References

I'm gonna quote this old article because yes.


PS.: It's the first time I post in here, so please, let me know (and sorry) if I'm doing anything wrong.

astearns commented 3 years ago

@GCSBOSS you did great, thanks for the proposal.

We did work on a multiple pseudo-element proposal around the same time as the 2012 blog post, but it was abandoned due to some objections people raised and lack of implementer interest. https://opensource.adobe.com/css-pseudo-elements/docs/css4-pseudoelements.html is one of the iterations, but I have not yet found the resolution we made to stop working on it.

GCSBOSS commented 3 years ago

@astearns Thanks for your reply. I would like to see some arguments. Hope more people show up here 😄


Edited with alternative syntax suggestion.

astearns commented 3 years ago

Some of the history: https://lists.w3.org/Archives/Public/www-style/2012Aug/0771.html

Loirooriol commented 3 years ago

For reference, an old spec draft allowed multiple ::before and ::after https://www.w3.org/TR/2003/WD-css3-content-20030514/#inserting0

rachelandrew commented 3 years ago

It would be interesting to see some of the specific issues you need to solve with multiple pseudo-elements. The ones I've heard from developers recently often relate to CSS Grid, and the fact you can't style (adding backgrounds, borders and so on) grid areas without adding an element. Those are probably better fixed by allowing styling of grid areas.

So, my question is whether the use cases you have are best solved with multiple pseudo-elements, or might they be better solved in some other way.

GCSBOSS commented 3 years ago

@rachelandrew Had this minutes before writing this issue.

off topic: By the way, scrim type of functionally would benefit from some ::behind type of pseudo element.

I believe most of these use cases are not well suited for semantic html elements.

SebastianZ commented 3 years ago

I'd also welcome to be able to stack multiple content generating pseudo-elements. There are definitely some situations in which you'd need them to style an element to avoid having to add extra DOM just for styling. Though having said that, many use cases can already be achieved by different CSS features or other pseudo-elements.

@GCSBOSS wrote:

* A button has an icon (icon-font pattern) on the `::before`

* a ripple effect in the `::after`

Ripple effects can also be done using the CSS Painting API. See https://codepen.io/iamvdo/pen/RwWVzar.

* now I need an overlay animation on top of the whole thing.

Depending on the use case this might be achieved by animating the filter property instead of using a pseudo-element.

* One might also wanna add any other styling/animation details to the same element

Exactly this requires concrete use cases which cannot be achieved by existing CSS features, or at least not without hacks.

* Maybe a scrim for modal functionally

off topic: By the way, scrim type of functionally would benefit from some ::behind type of pseudo element.

By "scrim" I assume you mean a backdrop. There is already a ::backdrop pseudo-element, which is meant to be used for that.

@rachelandrew wrote:

It would be interesting to see some of the specific issues you need to solve with multiple pseudo-elements. The ones I've heard from developers recently often relate to CSS Grid, and the fact you can't style (adding backgrounds, borders and so on) grid areas without adding an element. Those are probably better fixed by allowing styling of grid areas.

Which is discussed in #4416, btw. Also related to this are #499, #2748, and #5080.

Sebastian

GCSBOSS commented 3 years ago

@SebastianZ Thanks for your input 😃 !

Ripple effects can also be done using the CSS Painting API. See https://codepen.io/iamvdo/pen/RwWVzar.

It is an exciting API and probably way better performance. But coding CSS is so much simpler than js/canvas math D:

Depending on the use case this might be achieved by animating the filter property instead of using a pseudo-element.

I totally missed that. I guess there are just so many hours one can keep staring unblinkingly at a stylesheet. Thank you!

Edit: Though after a few attempts I could not reproduce a proper overlay-like flash over element contents without using another element/pseudo-element.

By "scrim" I assume you mean a backdrop. There is already a ::backdrop pseudo-element, which is meant to be used for that.

I believe that feature has some specific fullscreen behavior entangled in it. So maybe not fit for at least part of the use cases.

SebastianZ commented 3 years ago

@GCSBOSS You're welcome! 😃

Ripple effects can also be done using the CSS Painting API. See https://codepen.io/iamvdo/pen/RwWVzar.

It is an exciting API and probably way better performance. But coding CSS is so much simpler than js/canvas math D:

I assume most authors will not reinvent the wheel and just pick one of the existing solutions for that. That means, as CSS author you'd just add the paint worklet to your page and then only have to define the custom properties. And the JS/canvas solution is also more flexible as it can start the ripple effect at the cursor position.

Though there are pure CSS solutions for this as well, using a pseudo-element to achieve the effect, like http://mladenplavsic.github.io/css-ripple-effect/. And I can see the point of doing this with only using CSS.

Depending on the use case this might be achieved by animating the filter property instead of using a pseudo-element.

I totally missed that. I guess there are just so many hours one can keep staring unblinkingly at a stylesheet. Thank you!

Edit: Though after a few attempts I could not reproduce a proper overlay-like flash over element contents without using another element/pseudo-element.

It would surely help to clarify the need for multiple pseudo-elements if you posted the example code for what you want to achieve. Just show how you get the effect now and how it would be simplified if multiple pseudo-elements were available.

By "scrim" I assume you mean a backdrop. There is already a ::backdrop pseudo-element, which is meant to be used for that.

I believe that feature has some specific fullscreen behavior entangled in it. So maybe not fit for at least part of the use cases.

While it's currently defined in the Fullscreen API specification, it also works for the <dialog> element. There's also a note about that in the specification. Also have a look at the example in Codepen (which additionally uses the backdrop-filter property) to blur the backdrop.

What I want to say with that is that there is already a pseudo-element for backdrop/scrim effects. So this use case does not require stacking of pseudo-elements. And if there are still use cases related to backdrop effects which are not covered by ::backdrop yet, it should rather be discussed whether this feature can be extended to also cover them.

Having said all that, as I wrote earlier, in the past I also ran into a few situations in which I wished we had multiple pseudo-elements to avoid having to add real DOM elements just for styling. Unfortunately, I don't have a good example for that at hand, though. So people should come up with some real world use cases for them like the one from CSS Tricks in the initial post (which can also be achieved by using the CSS Paint API, btw.).

Sebastian

ByteEater-pl commented 3 years ago

Yea, adding something like more than one ::before or ::after, and even more often a wrapping ancestor (sometimes two) without messing with semantics, just for styling, as well as siblings (::before and ::after are children; it could be worked around with display: contents, but it has issues, e.g. causes loss of a natural parent in the box tree for the children from the document along with those inserted using pseudo-elements). It's so common, and doesn't motivate use of any scripting in a solution! Principle of least power FTW!

And we almost had it with XBL's generic div element in the XBL namespace http://www.w3.org/ns/xbl which could be used in templates and then applied to elements with the addition of the binding property to CSS. A fully declarative solution! Unfortunately, that was the time when browser vendors became particularly unwilling to implement anything specified modularly, not tied into the HTML5 überspec, and they rebooted the work on templates and Shadow DOM within that framework, with an approach based on scripting. Therefore, though unnecessary in many widely applicable, simple use cases (and with advancements in CSS also many not so simple), we're forced to use scripting, to have templates be HTML-only, and to work in a cumbersome way with some strange out-of-document trees, resulting from parsing template elements by a browser (as opposed to any other user agent).

There's still hope that things might get somewhat better in the next iteration. E.g. @tabatkins reminds from time to time that in his opinion (with which, as should be already obvious, I very much agree) enabling this functionality to be used declaratively remains a worthwhile goal.

faceless2 commented 3 years ago

I remember the first round of this, with ::outside(). We tried implementing it, it got out of control quite quickly.

I thought it worth reminding everyone about content: contents (https://drafts.csswg.org/css-content-3/#element-content). I don't know how widely implemented it is, but it can be used to move the DOM tree from an element to its pseudonode:

div {
    border: 1px solid blue;
    content: none;
}
div::before {
    border: 1px solid red;
    content: contents;
}

This will shift the DOM subtree from the div to its ::after pseudo-element - you're getting two borders around the subtree.

We've implemented this and it's fun to do things like self-captioning images:

img {
    content: none;
}
img::before {
    content: contents;
}
img::after {
    content: attr(alt);
}

But it's a bit of a hack, and falls down quite often - if the image has a border, for example, then the caption will too.

What we find ourselves wanting is a sibling pseudo-element, which creates a sibling to the current node. And in fact we effectively have one of these proposed in CSS already: https://drafts.csswg.org/css-gcpm/#footnote-call, but it's limited to footnotes.

Footnotes are content positioned out of normal flow, but a marker is left in the box tree where it was moved from. The way we implement this is to create a sibling pseudo-element before the positioned content. It's so useful, I don't get why it's restricted to footnotes:

In all these cases you might want to leave a call(*) in the paragraph referering to that content somehow, but you can't do this with a ::before or ::after on the floated content, because that would be moved with the content. Being able to do something like this:

figure {
    float: right;
    counter-increment: figure;
}
figure::call {
    content: "Figure " counter(figure) ": " attr(title);
}

would open up all sorts of possibilities, particularly when combined with content: contents.

GCSBOSS commented 3 years ago

@SebastianZ

I'll try and post some real-life examples soon.

Only to iterate on the <dialog> matter. abUsing the ::backdrop for anything other than a dialog would mean creating HTML elements for styling purposes, which defeats the purpose. Especially because <dialog> is not even a thing anymore (at least it was not, the last time I read about it).