w3c / csswg-drafts

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

[css-shadow-parts] Limited ::theme selector #3507

Open frivoal opened 5 years ago

frivoal commented 5 years ago

(Split from issue #3467, reported by @stefsullrew)

The current description of the ::theme selector is perhaps a bit too broad for real world usage. However the ability to access any ::part within the context of a component, without elaborate exportparts= chains would be much easier to work with in the context of a design system.

In the example below, a theme context is defined for **x-foo** with the prefix of “foo-. This would grant access to all child parts that match the name within the declared theme's context. This scoping limits the reach of ::theme while avoiding the potentially complex exportparts= chains. Particularly when there's deep composition of components not owned by a single team.

<x-foo theme="foo-">
    <x-bar></x-bar>
    <x-oof></x-oof>
</x-foo>

// x-bar definition
<x-bar>
    <p part="metadata">Posted on: 2019-01-10</p>
</x-bar>

// x-oof definition
<x-oof>
    <svg part="icon"></svg>
</x-oof>
x-foo::theme(foo-metadata) {
    color: grey;
    font-style: italic;
}
fergald commented 5 years ago

I think there's still a little missing from this. Imagine you had an outer component with 2 somewhat deep and complex inner components from different sources. This inner ones support themes but one uses foo-theme- prefix and the other uses bar-theme- prefix but the outer component would just like to present a single theme prefix.

We could support renaming themes in exportparts or we could support wild-card renaming in exportparts.

fredboyle commented 5 years ago

@fergald The main thinking behind this is to have a way to compose multiple "child" components from a variety of sources into a single custom component but avoid the need to have deeply nested exportparts= chains.

This thought was to avoid having a massive wrecking ball that could reach anywhere and make it more of a small strategic hammer.

Wildcards would still require exportparts= chains and in our ecosystem of teams creating new components from several nested others we want to make it as easy as we can to tweak the entire component as the author intends.

What are your thoughts on a potential solution for this? Would you please illustrate it with example markup?

andrewrabon commented 5 years ago

I've noticed that the ::theme selector has been removed from the spec published in November[1] as well as the most recent working draft from April[2]:

1. https://www.w3.org/TR/css-shadow-parts-1/ 2. https://drafts.csswg.org/css-shadow-parts/

Why was it removed and what was the context around the removal? Old versions of the spec seemed to include both ::part and ::theme.

Does ::theme still exist outside of this repo?

Edit- I see this is referenced in #3467! And the syntax there seems to be a better direction. I suppose my question is now, what is the status of ::theme?

fergald commented 5 years ago

The spec became a spec for part only to narrow the focus and get one thing agreed and implemented. Theme is not off the table, it should come back at some point, probably as a separate spec.

runarberg commented 4 years ago

Speculation: Wouldn’t :theme be better served as a pseudo selector inside the shadow DOM where consumers can select one or more themes setting one or more matching values on a new theme property?

I often see authors put stylistic attributes on custom elements (such as color or weight) and then consuming authors have to make sure to set the right stylistic attributes at the right place in the markup. For example say that a custom button has an optional weight attribute with slim and strong options. And my style-guide says all buttons inside tables should be slim, then I just have to remember to set this attribute each time I put a button inside a table. It would be much easier if I could just set a theme property—to something the author has carefully crafted and documented—in my stylesheet.

Example

/* inside shadow DOM */

:host {
  --background: var(--button-background, ivory);
  --color: var(--button-color, rebeccapurple);
  --color-disabled: var(--button-color-disabled, thistle);
}

:theme(danger) {
  --background: var(--button-bakground-danger, mistyrose);
  --color: var(--button-color-danger, firebrick);
}

button {
  background: var(--background);
  border: 2px solid currentcolor;
  color: var(--color);
  padding: 0.5ex 1em;
}

button:disabled {
  --background: var(--background-disabled);
  --color: var(--color-disabled);
}

:theme(slim) button {
  background: transparent;
  border: none;
  padding: 0;
  text-decoration: underline;
}

:theme(strong) button {
  background: var(--color);
  border-color: var(--color);
  color: white;
}

Then a consumer can write:

table my-button {
  theme: slim;
}

.danger-zone my-button {
  theme: danger;
}

dialog my-button[type="submit"] {
  theme: strong primary;
}

Why not ::part?

This gives the author of the custom element more power to carefully craft well designed elements using multiple properties on one or more child elements. With ::part consuming authors would need to copy and paste some of these styles, and be careful not to mix colors from different color scemes, and then apply on different shadow parts. Consuming authors would have an easier time selecting one or more of the documented themes.

castastrophe commented 4 years ago

We could support renaming themes in exportparts or we could support wild-card renaming in exportparts.

+1 to the idea of allowing support for wildcards inside ::theme

castastrophe commented 4 years ago

@runarberg You raise an interesting suggestion. At Red Hat, we've approached this in our web component project using data attributes as a means of opting into "variants".

<pfe-accordion pfe-variant="earth">

Perhaps what you describe could be a new idea under a different name such as :variant? I see your proposal as a little different than one of the original goals of ::theme, which was to provide a means for cascading styles into shadow DOM (including nested shadow DOMs) without having to use custom properties.

runarberg commented 4 years ago

Indeed what I’m suggesting is a little more powerful version of a mixin that is limited to the shadow root. But I feel like theme is the right name for it. The current suggestion if ::theme has a confusing name in my opinion. A theme is usually something curated by an author and then selected by a consumer. If I have to write my own styles inside a theme it doesn’t feel like a theme.

In my opinion what we now call ::theme would be better named something like ::broadcast-parts.

castastrophe commented 4 years ago

@runarberg I can see the perspective that the label theme might be a misnomer. I do like the idea of broadcast and your suggestion of broadcast-parts is more descriptive of what is happening (though I tend to prefer less verbosity when possible).

jouni commented 4 years ago

The approach @runarberg proposes is pretty much the same thing as we at Vaadin tried early on (https://github.com/vaadin/vaadin-themable-mixin/issues/12#issuecomment-327802249) as a way to control theme variant propagation and as a way for style sheet authors to have control over them instead of the HTML markup. I would very much like that to be the case, that it would be possible to choose predefined variants using a style sheet.

Unfortunately, our implementation ended up being terrible for performance and we moved back to the HTML attribute based solution.

This also reminds me of the idea @justinfagnani surfaced in another related discussion about theming shadow roots (https://github.com/w3c/webcomponents/issues/864#issuecomment-601924132). I see it as basically the same idea, but with a slightly different API.

The API proposed here feels like it would be easier to author. I don’t know if Justin had thoughts how the author defines the mapping between the “logical properties” and the final properties it is expanded into?

Comparing the two, from a consumer perspective:

mwc-card {
  --material-elevation: 2;
}
mwc-card {
  theme: material-elevation-2;
}

From the author perspective (shadow root of mwc-card):

:host {
  /* Not sure how --material-elevation would be processed? */
}
:theme(material-elevation-2) {
  box-shadow: var(--material-elevation-2-box-shadow);
}