Nolan Lawson has a little emoji-picker-element that is awfully handy and incredibly easy to use. But considering you’d probably be using it within your own app, it should be style-able so it can incorporated nicely anywhere. How to allow that styling isn’t exactly obvious:
What wasn’t obvious to me, though, was how to allow users to style it. What if they wanted a different background color? What if they wanted the emoji to be bigger? What if they wanted a different font for the input field?
Nolan lists four possibilities (I’ll rename them a bit in a way that helps me understand them).
- CSS Custom Properties: Style things like
background: var(--background, white);
. Custom properties penetrate the Shadow DOM, so you’re essentially adding styling hooks. - Pre-built variations: You can add a
class
attribute to the custom elements, which are easy to access within CSS inside the Shadow DOM thanks to the pseudo selectors, like:host(.dark) { background: black; }
. - Shadow parts: You add attributes to things you want to be style-able, like
<span part="foo">
, then CSS from the outside can reach in likecustom-component::part(foo) { }
. - User forced: Despite the nothing in/nothing out vibe of the Shadow DOM, you can always reach the
element.shadowRoot
and inject a<style>
, so there is always a way to get styles in.
It’s probably worth a mention that the DOM you slot
into place is style-able from “outside” CSS as it were.
This is such a funky problem. I like the Shadow DOM because it’s the closest thing we have on the web platform to scoped styles which are definitely a good idea. But I don’t love any of those styling solutions. They all seem to force me into thinking about what kind of styling API I want to offer and document it, while not encouraging any particular consistency across components.
To me, the DOM already is a styling API. I like the scoped protection, but there should be an easy way to reach in there and style things if I want to. Seems like there should be a very simple CSS-only way to reach inside and still use the cascade and such. Maybe the dash-separated custom-element name is enough? my-custom-elemement li { }
. Or maybe it’s more explicit, like @shadow my-custom-element li { }
. I just think it should be easier. Constructable Stylesheets don’t seem like a step toward make it easier, either.
Last time I was thinking about styling web components, I was just trying to figure out how to it works in the first place, not considering how to expose styling options to consumers of the component.
Does this actually come up as a problem in day-to-day work? Sure does.
I don’t see any particularly good options in that thread (yet) for the styling approach. If I was Dave, I’d be tempted to just do nothing. Offer minimal styling, and if people wanna style it, they can do it however they want from their copy of the component. Or they can “force” the styles in, meaning you have complete freedom.
I just started digging in to all shadow DOM subject, and ask my self the same question a few days ago.
I think the “@shadow name-of-element …” is a great solution.
Why don’t propose it to the CSS standardization?
This is one of my main sticking points about Shadow DOM, as well. I’m all for encapsulation and it is completely reasonable to me that styles defined internally on a component don’t leak out. However, more often than not, I do want global styles to enter in (not just the basic inheritable properties of, say,
color
). Yes, custom properties are one solution, and::part()
is another (that parallels styling the internals of UA-provided components, such as range inputs and media controls, e.g.::-webkit-slider-runnable-track
). The problem is that these increase the surface area of the API for each component and there’s no guaranteed consistency in naming conventions.Say I wanted to style all
<button>
s on a page the same way. In the light DOM, I can simply doBut let’s imagine that there are buttons in various custom elements from different sources, I need to know all of the properties for those components. The rules become something like:
This is a maintainability nightmare. And maybe some of those components didn’t expose a property for, say, setting the border radius, so they won’t match our design system.
Encapsulation leads to reusability only if we’re not tempted to reach into components and re-implement or fork them to fit our needs.
The defunct CSS
/deep/
combinator would only slightly improved the burden of maintaining global styles for custom elements with shadow roots.I find myself creating custom elements without shadow roots (vanilla with in lit-element) for this reason alone. However, shadow roots are useful when dealing with ID scoping, so I miss out on that.
The solution I’d like to see is an option to
attachShadow()
. Like specifying{mode: "open"}
to enable script access to shadow roots, passing an option (maybe{mode: "open", styles: "inherit"}
) that would allow page styles to flow into shadow roots would allow custom element authors to opt in to global styling, without breaking existing implementations. This wouldn’t require any extra syntax on the CSS side for component consumers to worry about, though there might be questions of specificity. If I want to style all stylable buttons, I’d just need a simple selector and not need to think about any custom elements. Styles inside the element would still be prevented from leaking out.I started looking into styling custom Web elements as I was making my Mimcss library to support them. I also think that the current approach is problematic. The main issue I have with it is that regular styles defined at the global level don’t apply to the shadow DOM. I really think it is a mistake. The fact that custom CSS properties do penetrate the shadow DOM is even more confusing: if custom CSS properties do, why the regular styles don’t?
Custom Web elements will definitely be used to deliver libraries of components. As a library author, I would like my components to easily adopt to the look-and-feel of the hosting application. In each component, there will probably be some “internal” UI aspects and mechanics that I would like to style as I, the library author, want, but the rest (likely a majority) should reflect the hosting application styles.
Encapsulation of styles defined under the shadow root for me means that they don’t leak outside: from my component’s styles to the hosting application. That is, I would like to see only one-way encapsulation.
As far as constructable stylesheets are concerned, it is a purely optimization feature. If you have multiple instances of a custom Web element, all the styles are defined only once but “adopted” by all element instances. The fact that a constructable stylesheet can be “shared” between the document and the instances of custom Web elements is nice if the custom Web element is built along with the application. If, however, custom Web elements come from a 3rd-party library, this sharing is useless. The library developer doesn’t know what stylesheets adopted by the document should be adopted by the components.