w3c / csswg-drafts

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

[css-pseudo-4] new generated content pseudo :between/:separator #2960

Open hfhchan opened 6 years ago

hfhchan commented 6 years ago

When building toolbars, navbars and lists, often there needs to be separators. They are currently implemented by using :before and/or :after but this is a hacky:

(1) the separator is not semantically a part of the toolbar item or list item itself; (2) the separator messes with hit-testing i.e. hovering the separator would either select the previous or next item which may not be preferred (3) relying on more specific selectors to turn off the selector at the start and/or end of the list.

The effect could be more elegantly accomplished by providing a way to insert generated content between a given node's children.

Example:

ul.recycler::between { display: block; content: ""; border-bottom: 1px solid #ccc; margin: 0 40px }

frivoal commented 6 years ago

Something like this could also be used for some cases of list styling:

li {display :inline;}
ul::between { content: ", "; }
ul::after { content: "."; }
:lang(jp) ul::between { content: "、"; }
:lang(jp) ul::after { content: "。"; }

You can do that with existing selectors though.

If what you're trying to put separators between is a bunch of buttons or a bunch of links (without a wrapping element), it's true that ::after would insert the separator inside of the buttons/links, which is not what you'd want.

Loirooriol commented 6 years ago

My ::contents proposal (#2406) would solve your use-case:

ul.recycler {
  display: contents;
}
ul.recycler::contents {
  display: block;
}
ul.recycler + ul.recycler::before {
  display: block;
  content: "";
  border-bottom: 1px solid #ccc;
  margin: 0 40px
}
tabatkins commented 6 years ago

...huh, ::between isn't a bad idea at all. ::contents isn't either. Both of them involve adding a substantial chunk of complexity in different ways.

Really I think there's been four variants of this idea proposed in the past, all with different strengths and weaknesses:

I suspect we'll want to add one of these at some point in the future, so it would be good to see which of these are easier/harder in the various engines.

frivoal commented 6 years ago

If we only go for one, my vote probably goes for ::contents, it seems more powerful and generally applicable. And if you add that, I agree we shouldn't get ::parent/::wrapper or ::outer-before/::outer-after, I am not sure it cancels the argument for ::between

tabatkins commented 6 years ago

@Loirooriol's example shows how you can achieve the ::between effect with ::contents. You can produce similar code with any of the others, too.

jonjohnjohnson commented 6 years ago

I don't think @Loirooriol's example cover's the 2nd point laid out by @hfhchan where ::between could make it so hit-testing is on the actual children items. The example might just need to alter ul.recycler + ul.recycler::before to ul.recycler li + li::before, adding in the li to the selectors? But still don't think that fully covers that point. Say, if it's not a ul with li inside, and instead just a div with a tags inside, I think forcing the use of ::before would make styling a:hover cumbersome, instead having to style a::contents:hover as well as clicking on the ::before would execute the link, unless it had pointer-events: none set?

All in all, I'm a huge fan of the ::contents proposal and am more excited to see that land. But it doesn't seem to cover the possibility of generated content being truly between siblings.

frivoal commented 6 years ago

Right. either I'm misunderstanding @Loirooriol 's example, or it doesn't work. His css seems to assume that there'd be multiple ul.recycler and puts pseudos between them, but the original example assumed a single ul.recycler with multiple children, and put pseudos between the children.

And I agree with @jonjohnjohnson's point: using the foo::contents with foo + foo::before does not fully replace ::between when foo is an interactive element like a link, as that interactiveness would still apply to the ::before.

tabatkins commented 6 years ago

Yeah, I was assuming that @Loirooriol's code was corrected to refer to the lis, not the ul. ^_^

Loirooriol commented 6 years ago

I fat-fingered the delete button of a previous comment, basically I said that I misread the proposal and that the code with ::contents needs some small modifications:

ul.recycler > * {
  & { display: contents }
  &::contents { display: block }
  & + ::before { /* ... */ }
}

It's true that proper hit-testing may need some refinements, like using ul.recycler > ::contents:hover instead of just ul.recycler > :hover, or playing with pointer-events. But I think it's mostly doable.

This table summarizes the relationships between the related proposals:

Feature Can be simulated with
::between ::contents ::wrapper ::outer-before / ::outer-after
::between - Yes, with display: contents and ::before / ::after. Only if ::wrapper::before / ::wrapper::after are allowed. Yes.
::contents Mostly not. - Only for use-cases without ::before nor ::after Mostly not.
::wrapper Mostly not. Only for use-cases without ::before nor ::after - Mostly not.
::outer-before / ::outer-after Mostly not. Yes, with display: contents and ::before / ::after. Only if ::wrapper::before / ::wrapper::after are allowed. -

IMO the best and most complete one is ::contents. ::wrapper may be good enough if pseudo-element nesting is allowed, but this would allow generating arbitrarily-deep structures which I suspect will be more difficult to implement.

jonjohnjohnson commented 6 years ago

From what I've gathered, ::contents has the best (if not only) shot at gaining implementor interest, because of it's nearly seamless use of display: contents and inheritance. So in my mind, the others aren't really on the docket.

css-meeting-bot commented 5 years ago

The CSS Working Group just discussed new generated content pseudo :between/:separator.

The full IRC log of that discussion <fantasai> Topic: new generated content pseudo :between/:separator
<fantasai> github: https://github.com/w3c/csswg-drafts/issues/2960
<fantasai> fantasai: I don't wanna do another generated content pseudo....
<fantasai> TabAtkins: Gonna discuss the problem
<fantasai> TabAtkins: Ppl want to address generated content siblings of an element
<fantasai> TabAtkins: There's a few variants that ppl want, but usually you want to address siblings, and before/after address children
<fantasai> TabAtkins: Sometimes it's about wrappersT
<fantasai> TabAtkins: There appear to be several different possibilities to address this
<fantasai> TabAtkins: First one, is a ::between pseudo
<fantasai> TabAtkins: It's like ::before/::after in that it creates children of an element. They sit between the children rather than before/after.
<fantasai> TabAtkins: Solves problem of ordered list with separators
<fantasai> TabAtkins: ul::between { content: " | "; }
<fantasai> TabAtkins: right now have to be careful about not styling last element or first element or whatever.
<fantasai> TabAtkins: Next one is ::contents, which is a wrapper element around all of the real children.
<fantasai> TabAtkins: This has a number of use cases. Ppl want extra wrappers and have asked for this before.
<fantasai> TabAtkins: Have to do it by putting more elements in the DOM
<fantasai> TabAtkins: Can be used like ::between in some cases, if you set the actual element to be 'display: contents'
<fantasai> TabAtkins: and then style the ::contents instead of the element itself
<fantasai> TabAtkins: Next one is ::parent/::wrapper, inverse of ::contents
<fantasai> TabAtkins: Creates a pseudo outside the element. More complicated wrt inheritance, because it flows uprward from originating element
<fantasai> TabAtkins: Last suggestion is ::outer-before/::outer-after. Creates siblings of originating element.
<fantasai> TabAtkins: travels sideways
<fantasai> TabAtkins: I think we should not have all of these. maybe one of these. Maybe none of these
<fantasai> TabAtkins: 'display: contents' makes some of these easier
<fantasai> TabAtkins: Question is do we want to do any of these, and if so which one?
<astearns> fantasai: I really don't like the way ::contents hides the thing
<astearns> fantasai: bad cascade ergo
<astearns> fantasai: between seems simplest
<astearns> fantasai: solves some use cases very cleanly
<fantasai> The ::contents solution affects how every part of your stylesheet interacts with this particular element. You want to solve this separator problem in this corner of your style sheet, but because you do it by hiding the actual element and moving all styles to the ::contents pseudo
<fantasai> every other part of your style sheet need to know that it has to style the ::contents pseudo of that element instead of the actual element.
<fantasai> This is fragile cascading and bad ergonomics for the author.
<tantek> Cascading is already too hard for authors, I tend to agree with fantasai that this would make the model worse (harder) for authors to predictably use / style etc.
<fantasai> emilio: Can we agree that whatever we do, that doesn't affect inheritance of the children? If you do ::wraper { color: green; } doesn't affet children of the elmeent
<fantasai> emilio: Because ::first-line has that weird thing and I prefer not having it
<fantasai> dydz: So the problem is that you had that horizontal list and you wanted separators, and you wanted vertical bar or character, and currently the person is syaying htere's no easy way to get at every other element, don't want to put a bar before the first one and not after last one
<fantasai> fantasai: That's part of it, it's nicer that it's easier to express but hte main problem is existing ::before/::after is child of the item, but you want a sibling of the item.
<fantasai> dydz: Why can't you use ::marker?
<fantasai> TabAtkins: Markers are also children
<fantasai> TabAtkins: They're inside the list item. If I put a border around it, it'll include the marker
<fantasai> myles_: So you want a sibling
<fantasai> TabAtkins: Yeah, at least in the box tree
<fantasai> jensimmons: To dydz's point, ppl who write CSS have a workaround for the specific case in the thread
<fantasai> jensimmons: but when we talk about possibility of creating a new pseudo element
<fantasai> jensimmons: My brain starts spinning about all the dissatisfactions we have about this
<fantasai> jensimmons: Sometimes we wish we had much more than ::before/::after
<fantasai> jensimmons: Sometimes we wish we had ones that were sblings
<fantasai> jensimmons: If we go down this road, think about all the ways that we want to have pseudo-elements, that's great.
<fantasai> jensimmons: But let's come up with something tha'ts robust and elegant, and solves this use case without creating new problems.
<fantasai> jensimmons: The scope of this problem is too small. It's a hint to a bigger scope. Coming up with a quasi-hack that solve sthis one thing is not as robust or complex as what we really need
<fantasai> futhark: What Jen is saying is, before and after can be same alis of ::between?
<fantasai> fantasai: Child list of an element is ::before, first child, ::between, second child, ::between, third child, ::after
<dbaron> We had something like this before, but I think concluded we wanted XBL (or its successor) instead.
<fantasai> jensimmons: Wouldn't be between, would be somehting else. You could have as many as you want. Like 17 of them. could determine if children or siblings
<fantasai> jensimmons: We have gaps in flexbox and grid, another way would be to style gaps or empty grid cells
<fantasai> jensimmons: would allow authors to style those things without them being real
<bkardell_> would it make sense to try to tackle the definitions there in houdini?
<fremy> q+
<fantasai> myles_: General solution to boxes is really interesting. But we don't want to go so far and make a whole DOM in CSS.
<fantasai> myles_: A competing proposal that is morepowerful and general would be interesting, woudl want to hear about it.
<fantasai> jensimmons: Most common case for pseudos that are siblings is decorating empty grid cells without putting empty markup in the DOM
<fantasai> TabAtkins: That one we definitely need to solve, I agree.
<fantasai> TabAtkins: That one is easier because it doesn't require where to insert between elements, can be a pile of ::befores
<fantasai> TabAtkins: I fear that a general solution would just re-implement the DOM, though.
<fantasai> fremy: I was going to say, I feel like in what we provide in CSS, I would be very glad if we have ::between which isreasonable for most use cases
<fantasai> fremy: If you want custom boxes, use Houdini
<fantasai> fremy: If you need 17 boxes, you need specific stylings for each
<fantasai> fremy: 17 random boxes is not useful
<fantasai> fremy: Grid is a good example of where we can describe this, but in the general case I think it will get more and more obscure
<dauwhe> https://www.w3.org/TR/2003/WD-css3-content-20030514/#inserting0
<fantasai> fremy: But if we do decoratively in CSS, seems nice, ::between is very similar to what we already have and it solves a lot of cases that people have
<fantasai> fremy: Having something simpler solution is better
<fantasai> fremy: I think ::between is the best way to solve this case. If you need more complex, Selectors is maybe not the right solution.
<fantasai> TabAtkins: We're talkign about boxes, but it's about elmeents.
<fantasai> TabAtkins: And custom layout is about fragments, not boxes.
<fantasai> TabAtkins: Let's look more into stuff.
<fantasai> TabAtkins: Jen brought up more possibilites, we should pay attention before tackling this.
<fantasai> jensimmons: The need is to ahve a way to lightwight put styling on things, like dividers between things or bg colors on particular grid cells
<fantasai> jensimmons: Want to do that in an elegant way, not limited to one or two.
<fantasai> jensimmons: We've been usign ::before/::after. We're reaching limit of those hacks.
<fantasai> jensimmons: I don't want to solve one use case. I want to solve the systematic problem at the right level.
<fantasai> jensimmons: I don't think it's just about lines between navigation elements, I think it's styling things on the page that don't exist in the content.
<fantasai> dauwhe: I run into this all the time. We want multiple borders in my world. Just nest a bunch of divs.
<fantasai> myles_: which is contrary to the possibility of CSS.
<myles_> S/possibility/philosophy/
<fantasai> TabAtkins: That feels a little different htan what Jen was talking about
<TabAtkins> s/TabAtkins/tantek/
<fantasai> tantek: Jen was talking about ways to style the negative spaces in CSS.
<fantasai> fantasai: Wrt multiple borders, we should solve that by actually having multiple borders.
<fantasai> astearns: Sounds like we're done for now, keep discussing later.
<fremy> q-
rik commented 5 years ago

Following https://twitter.com/AmeliasBrain/status/1100162917893234688, I'd like to express a use-case for replaced elements. In my first years, I tried several times to do something similar to:

input:required::after {
    content: "*";
    color: red;
}

I've also had to explain why this can't work to co-workers.

I could imagine wanting to prepend an icon like 🔗 to type=url or 📩 to type=email.

SebastianZ commented 5 years ago
jensimmons: We have gaps in flexbox and grid, another way would be to style gaps or empty grid cells jensimmons: would allow authors to style those things without them being real

For what it's worth, styling gaps is also discussed in #2748. And personally, I believe the use case of styling gaps in flexbox, grid and multi-column layout is better handled via some properties, because they avoid side effects by inheritance and styling unrelated to gaps. For implementers, this should also make it easier to handle, because it means no extra boxes have to be generated for them.

Having said that, there are still many other use cases, in which the aforementioned pseudo-elements would avoid having to do hacks or add elements only used for styling.

Sebastian

verdy-p commented 5 years ago

styling gaps is relevant for the layout only: the layout manager has its own requirements depending if this is a horizontal or vertical layout or a grid, and if flex is used (this becomes even more complex with multicolumn layouts, primarily used as containers for text, but not only) But there's a separate need for "::between" to generate contents, not necessarily boxes, without effect on the layout of these boxes. Styling lists with added punctuation/separators is different from styling gaps because they don't add necessarily any new box if the "between" content is a simple text or icon span (it could as well be breaks, or rules inserted inline but still within the same layout box.... So separate the design between generic content separators and layout-specific tuning of their gaps between layout-specific margins of each child (these gaps are complex also because of the need to manage collapsing margins when there's no gap).

Note that gaps may also have negative values (e.g. showing a grid of elements with no gap by default between children, but when hovering the grid with the mouse, negative gap would create a mask layer partially over the children without necessarily resizing or moving them, so the relative layouts of children would remain the same: this may be useful to perform visual selection in a way currently not supported by borders/margins which are always outside the content and never on top of them): negative gaps would then allow to show on top of existing margins, borders, paddings, and contents of the child.