-
Notifications
You must be signed in to change notification settings - Fork 669
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
[css-pseudo] Add a ::contents pseudo-element #2406
Comments
For what it's worth, similar ideas of inner and/or outer pseudo-elements have been proposed at various times going back at least 19 years. See for example: |
The old CSS 3 Content was killed with fire and allowed the creation of pseudo-elements outside the element, which is not consistent with Additionally, it allowed nested pseudo-elements, which either meant that they were created by the selector (ugly) or that the element tree was infinite (hard to implement?). I purposely avoided this feature in my proposal, at least for the moment. I had seen the #parent > :nth-child(1)::wrap(2), /* wrapper #1 */
#parent > :nth-child(2)::wrap(2) /* wrapper #2 */
{border: thick solid lime} <div id="parent">
<div>1</div> <!-- belongs to wrapper #1 -->
<div>2</div> <!-- belongs to wrappers #1 and #2 -->
<div>3</div> <!-- belongs to wrapper #2 -->
</div> So the result is not really a tree! I prefer to avoid this can of worms. But I believe my |
Could If we have |
For me,
|
For now, I think this proposal seems to be comparatively considerate, I cannot find a case to break compatibility with pages designed in the current standards. |
That does not sound good. You could always override the ones that you don't like when you turn |
This is so cool, I love it. |
You probably also should write down how this affects selectors, and in particular the child selector, or things like |
Yes, in this case setting some
Yes, it's one of my favorite features too. Making
Yes, selectors shouldn't be affected |
One of the reasons I keep pushing back on proposals like this is because they break cascading. Once you start styling |
(Sorry, wrong button.) |
Not sure I follow, styling
And that's so nice, but there's still only one (principal) box around the descendants. This greatly restricts what one can do in layout-related areas. |
@fantasai wrote:
Instead of having a pseudo element, why not introduce a new type of selector that targets only text contents as if it was a wrapped by a tag (directly above or below tag selector specificity). Let's apply all of those selectors/properties one by one from top to bottom, with section { color: red; }
/* red in section & sub-elems */
section * { color: blue; }
/* red in section, blue in section sub-elems */
section { color: green; }
/* green in section, still blue in all sub-elms */
section p { color: orange; }
/* green in section, still blue in all sub-elms but orange in p as sub-elem */
section @text { color: yellow; }
/* yellow in section and sub-elems but orange in p as sub-elem */
section > @text { color: purple; )
/* purple in section, yellow in sub-elems but orange in p as sub-elem */ ...etc. Obviously applying display modes such as In other words
|
@inoas I also support adding a pseudo-element to select text nodes (#2208). There is overlap in the case of an element which only contains text, but other than this
Text has no display type but in #2208 (comment) I proposed |
@Loirooriol Given that my suggestion above does not break the cascade (does it?) where would you still require Edit: I say to mimick this PR's elem > *,
elem > @text {
…
}
/* applied to all children nodes, wether they are regular elements or text nodes */ If you simply want to add wrappers with @fantasai on extending css properties. I am also a proponent of adding more semantic unique tags instead of adding divs as polymorphic containers. That way even with semantic structures you have an easier time to add wrapping styles. |
@inoas OK, I didn't read properly your previous comment. Now I think I understand the "break cascading" problem. In fact I would probably recommend against using section { color: red; }
/* red in section & sub-elems */
section * { color: blue; }
/* red in section, blue in section sub-elems */
section { color: green; }
/* green in section, still blue in all sub-elms */
section p { color: orange; }
/* green in section, still blue in all sub-elms but orange in p as sub-elem */
section::text, section * { color: yellow; }
/* yellow in section and sub-elems but orange in p as sub-elem */
section::text { color: purple; )
/* purple in section, yellow in sub-elems but orange in p as sub-elem */ But I don't really see this as a new problem against *::before { color: blue }
very#specific.selector { color: red } then |
Do you agree that having an element-style selector instead of a pseudo element type selector is more flexible as you can apply the cascade or use direct descendant operator/selector? Where would you see benefits of having |
I agree that exploring element-style selectors for tree-abiding pseudo-elements may be interesting. But I think of it as a separate selector-specific feature, not intrinsically related to nor as a replacement of In fact I don't understand why you think
Etcetera. |
And where is that a beneficial (more flexible/reusable / easier to grasp / shorter) syntax over #target { width: 100px; height: 100px; }
#target > @text { display: block; position: absolute }
#target { display: flex }
#target > @text { color: red; /* red flex item(s) */ }
#target::after { color: green; content: "green flex item after" }
What would happen in case of
Note sure I got the use case. Don't get me wrong, I rather see Edit: I re-read your proposal and see now you consider |
I understand that <section>
<div>
This <span>contains</span> a mix of <i>text</i> and <b>elements</b>
</div>
</section> section { display: flex }
div { display: contents }
div::before, div::after { content: "" }
div::contents { display: block } results in this box tree:
Using
Completely different. |
Here would be an idea for <div>
text1
<span>
text2
</span>
text3
</div>
<style type="text/css">
div > §inline { /* targets text1, span/text2, text3 as one node.
border 1px solid red would add one border around everything as all nodes are inline */ }
div §inline { /* as above but,
text2 inside span would receive another border because it is an inline node */ }
div > §text { /* targets text1 and text3 as separate nodes.
border 1px solid red would add one border around text1, one around text2 */ }
div §text { /* As above but because of the cascade
text2 will also have border 1px solid red applied, not only text1 and text3 */ }
</style> Link https://gist.github.com/inoas/8c66373a9f41e65fab988cb88feb7960 However I can see the difference as ::contents does not care about the sub types of the box tree. |
After reading through the history of this type of feature, I'm left feeling like this most recent proposal solves nearly every issue brought up over the years. And that it's pretty much unanimously a desired feature, except by implementors. @Loirooriol I'm sure you have a lot on your plate, but wondering what you think needs to happen next for this to move "forward"? And if anyone could get preliminary feedback from implementors? i.e. I can't wait to use this. |
@jonjohnjohnson I guess a Pseudo-Elements L5 draft should be created, and include my proposal there. This probably needs a CSSWG approval, and implementors may not like it. Also Pseudo-Elements L4 doesn't seem to be actively maintained, so maybe a new editor? |
I had a go at implementing this on an idle day a few weeks back, just to see how it would work. I think the concept is quite clean overall and, of all the proposed pseudo-element approaches this one seems the most coherent. But I think there's some clarification required around the interaction of the span::before {
content: '(A=' contents ')';
}
span {
content: none;
}
span::after {
content: '(B)'
}
<span>test</span>
(For those of you raising an eyebrow at this: here's the content property definition) My question is, if I then add a new style with the ::contents pseudo-element, where does it go? span::before {
content: '(A=' contents ')';
}
span {
content: none;
}
span::after {
content: '(B)'
}
span::contents {
border: 1px solid red;
padding: 0px 5px;
}
<span>test</span> There are two options here.
As @Loirooriol's draft proposal is now, you get option 1. Personally I think option 2 is more useful, however there are some caveats:
Actually that's all I've got. If we can get this resolved one way or another, I'll have another go at implementing. I'd also like to point out that the |
Oh there is another point I wanted to make. I think it might be a lot easier if the descendants don't inherit from ::contents. That should get you past a lot of the concerns. Elements are styled according to their location in the DOM, exactly as they are now, and a This approach is consistent with span::before {
color: red;
content: '(A=' contents ')';
}
span {
content: none;
}
span::after {
content: '(B)'
}
<span>still in black</span> In the box tree, the text content of the Put another way: almost nothing inherits from span::before {
color: red;
content: '(A=' contents ')';
}
span {
content: none;
}
span::contents {
color: blue;
border: 1px solid red;
display: inline list-item;
}
span::after {
content: '(B)'
}
<span>test</span> Then the ::contents is getting generated content of its own. I think the result here should be: When I say "we had a go at implementing it", this was the approach we took. |
It has happened multiple times that I wanted to style the contents of an element, but excluding ::before and ::after. I was trying to cover this usecase. I guess what you are proposing is that children elements in the DOM would remain being children in the element tree, they would just generate boxes inside ::contents. I agree this makes inheritance simpler, but seems less useful and it's not clear where ::contents would fit in the element tree. Between ::before and the first child? Between the last child and ::after? |
That's my working mental image, yes. I've had another read through your full proposal, I think it's exactly the box model you proposed, just without the impact on inheritance. So I'm not really clear on what you mean by where it would fit in the element tree - you mean for styling? As a pseudo-element it would inherit from it's owning element, exactly as for ::before. I suspect I've misunderstood the question! Here's another way to phrase what I'm suggesting.
The element's default value of I've looked through the examples from your proposal and I think this will work for all of them. If there are cases where you think it's less useful, lets take a look and see if we can figure them out. But I am struggling to imagine a case where you'd want the element's children to inherit styling from the ::contents pseudo-element. The best example I could come up with that you haven't illustrated already was automatically generating table cells: td {
display: contents;
}
td::before {
content: attr(header);
display: table-cell;
}
td::content {
display: table-cell;
}
<td header="foo">bar</td> to give the box model
|
I assume you mean |
If my idea of |
I would like to suggest that, like And can we just use the singular form ( |
Maybe it's worth noting that, technically, this example already kind of works due to automatic box tree fixup algorithm of the table layout (with "bar" becoming an anonymous table-cell box next to the table-cell box generated for |
@ianthedev See https://drafts.csswg.org/selectors/#pseudo-element-syntax
|
I got into this "issue" by performance problems in an animation. I want to blur out the page content when an overlay is shown. The page gets a #page {
opacity: 1;
transition: opacity 1s;
}
#page.blur {
opacity: .3;
filter: blur(10px);
} The logic is simple: It should apply the blur filter once and then fade out the hole thing smoothly. But what I get is a horrible stuttering; the blur filter gets recalculated for each frame. If I hovever put the hole |
I was trying to see how far I could get with avoiding wrapping elements to achieve sections as full-width bands. There are some exciting CSS things being discussed: * A [::wrapper pseudo element](w3c/csswg-drafts#588) seems stalled because it could cause some tricky situations in the DOM, but * A [::contents pseudo element](w3c/csswg-drafts#2406) is still under consideration and has some steam.
6 years later, this feature has become even more desirable, thanks to container queries. Container queries, as the name implies, can only query containers. Today this means we often need to add a wrapping DOM element to be able to query it. With something like .thing {
container-type: inline-size;
&::contents {
@container (width < 20ch) {…}
}
} This pattern would similarly be incredibly useful for container style queries. button::contents {
@container style(--variant: cta) {…}
} <button style="--variant: cta"> |
I have a related use case: wanting to set lengths based on a container’s size, for the container itself. I tried to do this:
I ended up wrapping the content of the container in a
The @fantasai explained to me that there would be major cascade problems with a pseudo-element like this, but as there are use cases for being able to do things like what @mayank99 and I described, it would be good to have a way to make them possible without adding elements. |
While the cascade concern makes some sense to me, it also strikes me as saying "if you can put a |
One thing I was thinking about regarding the And while the proposed “ What if we had a non-inherited property that would enable the The This won't affect any existing code, and we'd need to explicitly enable the pseudo-element on the elements where we'd want it. It would behave just as any other element, in that anything inside of it will inherit from it, not from the element itself. Someone could do I would be curious to know what are the potential issues (cascading or otherwise) with this kind of approach. |
If the |
I like that idea. And then, the default style for it could be Although, there might be cases where someone could want to have such an element, but the only thing I can think of is something like “I want to pass some inherited property down to the children without actually applying it on the element itself”. This could be mostly worked around via With |
What are you trying to achieve by requiring a new property? The only advantage that I see is that it would allow inheriting as normal, without requiring Note that even if we say that |
If we'd just use the I am not a browser engineer, and if it could be done in a way that every element will inherently have a |
The advantage of always generating it is that if you want to use it, it's easier. Compare:
Not a big fan of basing the existence on |
My preferred way would be no boxes by default, and thus no custom inheritance or default #a::contents { display: contents; color: blue; }
#b::contents { display: block } If we could have some non-contents value as the initial non-generating value and allow While the |
I agree with the "no custom inheritance or default I'm not sure if a new Another thought: If we're adding a new value to an existing property, should we do it for #a::contents { content: bikeshed; color: blue }
#b::contents { content: bikeshed; display: block } Maybe this is a not good idea, since |
I agree
This implies that only the inheritance of The example above would be: #a::contents { content: normal; color: blue }
#b::contents { content: normal; display: block } No need to add a new |
I'm mostly ok with using the |
I'm also mostly ok with I also just noticed #a::contents { content: contents; color: blue }
#b::contents { content: contents; display: block } |
I planned to use |
Some years ago I proposed a
::contents
pseudo-element. I have been refining the details in https://github.com/Loirooriol/css-contents, and yesterday someone proposed the same idea, so I decided to file this issue.To summarize, elements generate a
::before
pseudo-elements at the beginning of their contents, and an::after
one at the end. This proposal wraps the contents inside a new::contents
pseudo-element.In some way this is the opposite of
display: contents
: it allows you to insert a box between an element and its children, without needing to change the HTML in non-semantic ways.You can see all the details and examples in https://github.com/Loirooriol/css-contents, but the behavior is:
::contents
hasdisplay: contents
by default, via a rule in the UA origin. This can be overridden so that it does generate boxes.::contents
can be styled with arbitrary properties.::contents
has no effect in replaced elements.::contents
inherits from its originating element.::contents
originated by the parent element. For non-inherited properties, inheritance is directly from the parent element (to avoid breaking theinherit
keyword).::contents
for all properties, and assignall: inherit
to::contents
in UA origin.The text was updated successfully, but these errors were encountered: