Re: [csswg-drafts] [css-shadow-parts-1] Can class name selectors apply to a part? (#3431)

I've done an initial review of our [Elix component library](https://component.kitchen/elix) to look for situations where someone might want to style a part based on component state or part state. These components, roughly 60 in number, are intentionally generic in appearance so that they can eventually be styled to match an app's visual aesthetic or branding, so they seem like a reasonable corpus of use cases for shadow parts.

# Initial findings

* 26 components have parts with a binary part state, like the `selected` example in the [TabStrip](https://component.kitchen/elix/Tabs) example mentioned above.
* 21 components have parts with state reflected by a string that can take one of a limited set of values; i.e., an enum. Example: the [MenuButton](https://component.kitchen/elix/MenuButton) component has a `horizontalAlign` property that governs the alignment of the popup menu with respect to the button that invokes the menu. This `horizontalAlign` property takes one of 5 enumerated string values: "end", "left", "right", "start", or "stretch". If a `MenuButton` is exposed as a part, the designer may want to conditionally apply styling to that part based on that menu's horizontal alignment.
* 6 components have 2 or more parts that each expose part state, in some cases the same kind of state but with different visual semantics. E.g., a stock [Carousel](https://component.kitchen/elix/Carousel) component has two subcomponents: 1) a sliding panel showing images or other content, and 2) a row of dots indicating which item is shown. Each subcomponent has elements it will want to expose as parts, and the outer `Carousel` will want to forward those parts to the outside world to make them available for styling. Significantly, both subcomponents support a selected state, but the visual semantics of selection differ: for the panel showing images/etc., the selected state governs visibility; for the dots, the selected state governs brightness or some other means of visually indicating selection.

So out of our 60 components, it looks like we'll hit this issue in a substantial portion of them.

I think allowing multiple part names will cover the scenarios I can envision so far. That said, for the 21 cases described above where state is essentially an enum, it'll get cumbersome to define part names for each enum value. In the `horizontalAlign` property, there are 5 possible string values, so we'll need to generate one of 5 part names to tack onto the part: `horizontal-align-end`, `horizontal-align-left`, `horizontal-align-right`, etc.

# Part attributes

As I think through using multiple part names to indicate part state, I'm becoming worried that it'll get quite complex.

The problem is that a `MenuButton` knows what values of `horizontalAlign` are valid. A component using a `MenuButton` instance in its shadow does not — but it's that outer component that will need to stick a `part` like `horizontal-align-left` on the `MenuButton` instance. That means the outer component needs to track the current value of the menu button's `horizontalAlign` property ("left", etc.), munge it to some part variation (`horizontal-align-left`, etc.), and manage that on the part. That's non-trivial JS that needs to be written for any parts with styling that might depend on component or part state. It could be made to work, but will be cumbersome.

The reason I suggested considering something involving attributes is that native HTML elements already reflect their inner state as attributes for styling purposes. It'd be reasonable to ask component authors to do the same for those properties that might be implicated in styling.

We can easily extend our `MenuButton` component to reflect a `horizontal-align="left"` attribute on itself when the underlying `horizontalAlign` property changes. If the `MenuButton` is being using directly in light DOM, then style rules can be written to target that attribute value. If the `MenuButton` is being used as a part, the page author can write style rules that target the part name and the attribute value. That is, styling something directly or as a part would be the same.

> I think there would be ergonomic advantages to [attr="value"] if you were able to set attributes indiviually as you can on a HTML element but if you're setting them inside part-attribute="attr=value" then there's very little win on top of just doing part="attr=value" which gives you a part named attr=value.

My suggestion was not to let someone _set_ attributes inside `part-attribute`. Rather, the `part-attribute` would give a, say, space-delimited list of part attributes that the component author wants to expose to the outside.

Example: Suppose a `MenuButton` has two properties/attributes that might be interesting to people styling the component: `horizontalAlign` and `opened`. Someone using the menu button as a part could write:

```html
<menu-button part="menu-button" part-attributes="horizontal-align opened">
```

Or, if they only want to let someone style the "opened" attribute on the part, they write `part-attributes="opened"`. That is, `part-attributes` means something like, "attributes someone styling this part might want to target". Another name might be `styleable-attributes`.

The `MenuButton` instance then reflects the current state of the `horizontalAlign` and `opened` properties as attributes. Someone using a menu button in light DOM can write:

```css
menu-button[horizontal-align="left"] { ... }
```

And someone targeting a `MenuButton` as a part would write:

```css
my-component::part(menu-button)[horizontal-align="left"] { ... }
```

That is, it's essentially the same syntax.
* It's also the same work for the person writing `MenuButton`, who knows the component best anyway.
* This is consistent with how someone writes CSS targeting native element states via attributes.
* People using `::part()` could potentially use the full range of attribute selectors (`[attr~=value]`, etc.), not just existence/non-existence.
* It's less work for the person using `MenuButton` as a part. That person just needs to know whether they want to let someone write styles like the above example, and they can capture their decision in markup. They don't need to think about values of `horizontal-align` or add/remove `part` values dynamically in JS.

> We also need to think about forwarding from deeper shadow-trees. Would we need a way to forward and rename attributes?

FWIW, I believe that, if the person exposing a part deems it reasonable to expose an attribute on that part for styling, that other component authors further up the chain would probably be fine leaving such flexibility in place as they forward the part. It's zero additional work for them. That is, I don't think someone forwarding a part will need to narrow the set of attributes available for styling.

Apologies if the examples are abstruse. The number 1 request we get from potential users of our library is whether they can style the components in CSS, so we're interested in making this easy for those users. If it'd be helpful to talk through any of this, I'll make myself available for a chat.

-- 
GitHub Notification of comment by JanMiksovsky
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/3431#issuecomment-446808478 using your GitHub account

Received on Thursday, 13 December 2018 01:29:14 UTC