Re: [w3c/webcomponents] Support Custom Pseudo-elements (#300)

@seaneking I agree. It was just something I was considering as a workaround for our theming use case in [vaadin-core-elements](https://github.com/vaadin/vaadin-core-elements) and wanted to hear how others feel about it.

---

@rniwa How likely/official is that (getting custom pseudo elements added to the spec)?

---

I feel like I want to contribute some more thoughts about this proposal, and why I also think custom pseudo elements are needed and `@apply` is not sufficient by itself.

I’ve come to the same conclusion as Philip, that `@apply` has shortcomings together with component/element states. The use case I have trouble currently is a data table row (in [vaadin-grid](https://github.com/vaadin/vaadin-grid) to be more exact), and how to provide styling hooks for that.

```html
<data-table>
  #shadow
    ...
    <div class="row selected">
      <!-- Light DOM -->
      <span>Cell data</span>
    </div>
    ...
</data-table>
```

The rows can have different states, such as selected, expanded, hover, etc.

Now, with `@apply`, we get something like this:

```css
.row {
  @apply --data-table-row;
}

.row:hover {
  @apply --data-table-row-hover;
}

.row.selected {
  @apply --data-table-row-selected;
}
```

To me, those mixin names have a bad code smell. It feels like they are just exposing the selector, but with a less semantic syntax. I’ve considered if we should use BEM like syntax for mixin names (`--data-table__row--selected`), but that feels like an ugly workaround as well, exactly what we should be fixing with Web Components (not having to come up with naming conventions like this).

Also, with `@apply`, the component author dictates the specificity of those “selectors”. The user can’t style a selected row which is hovered differently from regular rows that are hovered. Also, if I want a hovered row background to be `#eee`, and the selected row background to be `#e6e6e6`, I no longer have any hover style for my selected rows, and I have no way to reverse that specificity (have row hover override selected row styles). That would require another mixin (`--data-table-row-selected-hover`), just like Philip points out in his proposal as the main problem, but it would still have the specificity problem.

User styles:
```css
data-table {
  /* The order of these mixin declarations do not affect the specificity at all */
  
  --data-table-row-hover: {
    background-color: #eee;
  };
  
  --data-table-row-selected: {
    background-color: #e6e6e6;
  };
  
  /* Additionally if needed */
  --data-table-row-selected-hover: {
    background-color: #e1e1e1;
  };
}
```

`@apply` feels more like the component reaching to the outside to grab something the author has good expectations of (high-level global font/color declarations for example), whereas custom pseudo elements feel like the component offering a way for users to reach inside the component to provide customized styles when the component itself can’t really be expected to know how it should look like. `@apply` feels like it’s inverting too much control.

As a comparison, we would do the following with custom pseudo elements:

```html
<data-table>
  #shadow
    ...
    <div pseudo="row" class="selected">
      <!-- Light DOM -->
      <span>Cell data</span>
    </div>
    ...
</data-table>
```

User styles:

```css
/* The order of these declarations affect specificity as expected */
data-table::row(:hover) {
  background-color: #eee;
}

data-table::row(.selected) {
  background-color: #e6e6e6;
}

/* Additionally */
data-table::row(.selected:hover) {
  background-color: #e1e1e1;
}
```

With custom pseudo elements, we also gain the ability to style the light DOM nodes depending on the pseudo elements and their state, which is impossible with `@apply` (without nesting support at least).

User styles:

```css
data-table::row span {
  color: #000;
}

data-table::row(.selected) {
  background-color: blue;
}

/* This is impossible with mixins */
data-table::row(.selected) span {
  color: white;
}
```

The same with `@apply/nesting`:

```css
data-table {
  --data-table-row: {
    & span {
      color: #000;
    }

    &.selected {
      background-color: blue;
    }

    &.selected span {
      background-color: white;
    }
  };
  
}
```

Not sure what other implications (performance?) this kind of “light DOM element targeting inside pseudo elements” would have.

This data table use case might not be the best one, as you could argue those `<div class="row">` elements should be in the light DOM, authored by the user (for example as `<data-table-row>` custom elements). But as it is currently, those elements are generated at runtime, and the light DOM contents are generated based on user provided templates (basically how `<iron-list>` works). So it’s a use case at least for us.

---

I was actually unaware that nesting might be added to the spec and that it could work together with mixins. That might be a solution to these use cases as well. Personally, I like the pseudo element syntax more, though.

---

Lastly, I wanted to try and provide “answers” to the issues Tab pointed out with custom pseudo elements:

> Are shadow-pseudos more like classes or IDs? That is, do they need to be unique, or is it ok to have multiple selected by the same name?

As should be obvious from my use case above, I think shadow-pseudos should be more like classes, and allow selecting multiple elements.

> If you can have multiple, how do we select a single one to style? Does the component author have to allow this?

If you want to select a single one, I think it should work like regular light DOM, i.e. `data-table::row:first-child`, and the author allows this by adding the `pseudo` attribute to all the elements that they want to get selected.

> Can a single element expose multiple pseudo names for itself?

Can’t think of a use case for this.

> How do we handle both a parent and child exposing themselves as pseudo-elements? Is the nesting visible or not? If yes, can you write ::foo::bar?

Not sure how necessary it would be to allow nested pseudo-elements, but I think I would expect `date-picker::date-cell.selected::before` to work.

> If a component contains other components, and wants to expose some of its sub-components parts as pseudo-elements, how does it surface them? Are they selectable by default somehow (if so, how do you handle namespacing)? If not, how do you expose them? Is it apparent that they come from a sub-component, or can the parent component make them look "native"?

Without spending too much time thinking about this, I don’t feel a strong need to expose pseudo-elements outside another shadow boundary. I guess `@apply` could cover those cases.

For example:

```html
<x-foo>
  #shadow
    <style>
      date-picker::date-cell {
        @apply --x-foo-date-cell;
      }
    </style>
    <date-picker>
      #shadow
        <div pseudo="date-cell"></div>
    </date-picker>
</x-foo>
```

But I admit, this is a tough issue, and Philip’s proposal doesn’t cover this, apart from proposing that custom-pseudos should always be prefixed with the custom element name.

I also admit I have no idea what kind of performance problems custom pseudo elements could cause if they can cause similar problems as `/deep/`.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/webcomponents/issues/300#issuecomment-271281935

Received on Monday, 9 January 2017 13:18:42 UTC