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

CSS selector to style slots with slotted elements #936

Open
justinfagnani opened this issue Jul 8, 2021 · 18 comments
Open

CSS selector to style slots with slotted elements #936

justinfagnani opened this issue Jul 8, 2021 · 18 comments

Comments

@justinfagnani
Copy link
Contributor

justinfagnani commented Jul 8, 2021

We've seen a number of elements that style slots based on whether the slot has slotted contents.

Right now these elements use the slotchange event to look at assigned elements and set some state that triggers rendering of the class. This approach can lead to extra rendering work and complicate server-side rendering.

It would nice nice to skip JS completely for this styling use case and be able to use some selector, possibly a pseudo-class, to style the slot:

#label-slot:hasSlotted(*) {
  padding: 0 8px;
}

Maybe it's possible that :has() will be useful for this case, but I'm not sure. Does this selector even make sense with mixed scopes: #label-slot:has(::slotted(*))?

@byung-woo
Copy link

byung-woo commented Jul 14, 2021

IMHO, :has should provide that functionality. When we have .a .b relation, we can select .a with :has like this - .a:has(.b). Same with this, when we have #label-slot ::slotted(*) relation, we can select #label-slot like this - #label-slot:has(::slotted(*)).

I'm currently prototyping the :has spec in the chromium project, and it is very beginning stage. I think that the :has(::slotted(*)) cases will be checked during the prototyping.

@rniwa
Copy link
Collaborator

rniwa commented Jul 14, 2021

IMHO, :has should provide that functionality. When we have .a .b relation, we can select .a with :has like this - .a:has(.b). Same with this, when we have #label-slot ::slotted() relation, we can select #label-slot like this - #label-slot:has(::slotted()).

I don't think we should do that. :has should be strictly about the tree in which this pseudo selector is evaluated. Otherwise, it would result in very pathological O(n^3) behavior in some cases.

@emilio
Copy link

emilio commented Jul 14, 2021

::slotted is a pseudo-element of the slot. I don't think slot:has(::slotted(..))) makes much sense, in the same way slot:has(::before) and such should probably not work.

Maybe :empty should look at flat tree children, or maybe we just need a separate pseudo-class, but yeah using :has for this feels wrong.

@bathos
Copy link

bathos commented Jul 14, 2021

I'd guess that :empty can't change in this regard, but I think it's not quite the same question anyway. At least in my experience when we do this we're toggling a style on "does the slot have assignees" - but the slot might actually have default content, so it might never be empty in the flat tree.

I once wrote slot:unassigned in css, apparently just imagining it was a thing that already existed :)

@byung-woo
Copy link

Oh, I misunderstood the intent of this issue. Sorry for the confusion.

:has should style any ancestors of slot element, but it cannot not style the slot element. I think we can check like this. If li ::slotted(*) works, then li:has(::slotted(*)) should work. But slot ::slotted(*) doesn't work, so slot:has(::slotted(*)) will not work either.

@justinfagnani
Copy link
Contributor Author

Sorry that I mentioned :has() at all.

I do think we'd need a new pseudo-class for this like :hasSlotted(<selector list>).

@dbatiste
Copy link

I've encountered the need for this many times and I think this would be useful. However, sometimes, knowing whether the slot is empty or not isn't quite good enough because we want to know if the slot contains visible elements. If only I could have :hasSlotted(:visible) 😄 .

The visibility bit aside, I think this should work for the case where there are nested slots. Ex.

outer-component
   --shadowDOM
      <inner-component>
         <slot name="outer" slot="inner"></slot>
      </inner-component>
      <slot></slot>
      ...

inner-component
   --shadowDOM
      <slot name="inner"></slot>
      ...

Given such components, usually I would be interested in the flattened assigned nodes. That is, I would usually consider the inner slot not empty in this case...

<outer-component>
  <div slot="outer">...</div>
   ...
</outer-component>

But I would usually consider the inner to be empty in this case, even though it contains a slot.

<outer-component></outer-component>

@justinfagnani
Copy link
Contributor Author

justinfagnani commented Jul 17, 2021

I would think that :hasSlotted(S) should match any time ::slotted(S) matches something, so yes this would be for the flattened tree.

@justinfagnani
Copy link
Contributor Author

To show an example where slotchange and JS is used to fill this gap, I noticed this pattern in @claviska's Shoelace components.

A component that wants to style based on the presence of slotted nodes will compute a class based on a JS utility:

    return html`
      <button
        part="base"
        class=${classMap({
          'button--has-label': this.hasSlotController.test('[default]'),
          'button--has-prefix': this.hasSlotController.test('prefix'),
          'button--has-suffix': this.hasSlotController.test('suffix')

this.hasSlotController.test('[default]') returns true if the default slot has assigned nodes, which causes the element to have the button-has-label class. Then it's styled with:

  .button--has-label.button--small .button__label {
    padding: 0 var(--sl-spacing-small);
  }

and there's a whole HasSlotController utility that will listen for slot change and re-render the component so that those classes can be recomputed.

I think this whole mechanism and render cycle could be replaced with this CSS if we had the right selector:

  button:has(slot:not([name])::hasSlotted(*)) {
    padding: 0 var(--sl-spacing-small);
  }

@Westbrook
Copy link
Collaborator

So, so much this! At Adobe we currently leverage a mixin for this https://github.com/adobe/spectrum-web-components/blob/main/packages/shared/src/observe-slot-presence.ts and the related has assigned text functionality https://github.com/adobe/spectrum-web-components/blob/main/packages/shared/src/observe-slot-text.ts having this functionality by default without keeping track of slot changes would be amazing.

It’s too bad there was initial pushback on :has as I think that would be an amazing approach :host(:has(::slotted(input))) seems so right for the customization of an input leveraging Decorator Pattern Plus or similar and needing to decide whether you’ve received an input or created one.

In the bike shed, I’d love for this to better match the JS API of slot.assignedElements and el.assignedNodes and see something like slot:hasAssignedElements(button, a) or slot:assignedNodes as the selectors here so that you could be specific in both directions.

Would be very excited to bring to the Web Components Community Group for further feedback!

@rniwa
Copy link
Collaborator

rniwa commented Apr 1, 2022

Is the use case here involve differentiating assigned nodes vs. default content? Or is it about having any content at all?

It would be useful to have a list of concrete use cases for CSS WG folks to study & come up with an appropriate abstraction / name for this feature.

@dbatiste
Copy link

dbatiste commented Apr 1, 2022

In the cases that I've come across in our library, we would want to select if there is either assigned or default content.

@bathos
Copy link

bathos commented Apr 2, 2022

Is the use case here involve differentiating assigned nodes vs. default content? Or is it about having any content at all?

I found ten places where this occurs in our codebase where assigned vs unassigned is the condition. Most of them don’t have default content, but two do.

It would be useful to have a list of concrete use cases for CSS WG folks to study & come up with an appropriate abstraction / name for this feature.

I’m working this out backwards a bit, so not a high-level use case, but since there are some very clear patterns here it may still be useful info: Most of these are toggling hidden on a containing “grid cell” element (or doing something with the equivalent effect: “this cell doesn’t exist if the slot’s empty”). The more complex remainder could still be roughly summarized as “making a CSS grid respond to assignedness”.

@rniwa
Copy link
Collaborator

rniwa commented Apr 5, 2022

It would be useful to have a list of concrete use cases for CSS WG folks to study & come up with an appropriate abstraction / name for this feature.

I’m working this out backwards a bit, so not a high-level use case, but since there are some very clear patterns here it may still be useful info: Most of these are toggling hidden on a containing “grid cell” element (or doing something with the equivalent effect: “this cell doesn’t exist if the slot’s empty”). The more complex remainder could still be roughly summarized as “making a CSS grid respond to assignedness”.

Interesting. Perhaps there is a grid specific solution that we can make here as well. Not that we'd not want this feature separately but depending on what you're trying to do with grid, adding new capability to grid to resolve the particular use case might make sense.

@trusktr
Copy link

trusktr commented Jul 29, 2022

Related: #889

@Lonniebiz
Copy link

I want complete control over the styling of slots and all their children. I wish there was a flag we could flip so that slots would ignore non-shadow styling. A slot is really just a really ugly argument to a function. If you pass something into my function (custom element) I should be able to do what ever I want to that input and all its children too.

The whole goal, for me, is to have every aspect of my web component contained in one file and I don't like having to fool with document level CSS to style children of shadow slotted element. I can still do what I'm wanting with hacks and tricks, but allowing shadow level CSS to style children of slotted elements would make the whole experience much nicer.
#889 (comment)

@castastrophe
Copy link

Is this in line with this CSS issue? w3c/csswg-drafts#6867

@keithamus
Copy link
Collaborator

WCCG had their spring F2F in which this was discussed. You can read the full notes of the discussion (#978 (comment)), heading entitled "Styling empty slots".

In the meeting it was pointed out that - as already mentioned above - this may be solved by w3c/csswg-drafts#6867.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests