-
Notifications
You must be signed in to change notification settings - Fork 375
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
Web components should be able to easily adapt to the host page while maintaining enapsulation #986
Comments
See also: #909 |
Trying to adhere to the way native components works is possibly one of the best solutions. Where outside specificity easily overrules component-level styles. I think a main problem occurs because custom web components generally are far more complex and deeper than form controls and the like. They end up involving a whole series of children. Native components involve 1 text style or 1 background style more often than not. Whereas custom webcomponents can easily see 2 type/color treatments. While the goal is to not style components, the user of a component may have special I think webcomponents having weaker specificity by default, allowing things to overridden would be good. That will involve some unintended side effects when implementing some in your code. But I also think anyone who implements one will see that straight away. The other side is more easily invoking the shadow DOM later in the chain. Rather than it being binarily on, encapsulating everything, or not on at all. |
At work we just have a snippet that ends up being copied into nearly every web project that essentially just loops over |
Erm, I am linking to this in my first post:
Not sure what you mean. Outside specificity does not overrule anything in shadow dom in native components.
There are some pretty complex native elements, e.g.
This is not a specificity problem at all. I think you may be referring to another issue (potentially the problems discussed in w3c/csswg-drafts#7922 ?).
I think this is the point of #909.
I think this use case is actually better served by |
Just to throw this idea out: <my-component>
<template shadowrootmode="open">
<style>
/* Basic case */
button { all: outside }
/* Inside of the form element, just pull in outside styles, like if it was slotted */
@scope (form) to (whatever-nested-component) {
* { all: outside }
}
a { color: red; font-size: outside; }
</style>
</template>
<button>Do Things</button>
<a class="fancy-link">Wouldn't want ANY outside styles messing with this super complex link styling (Except the font size, that's fine)</a>
<form>
<!-- some form controls here -->
</form>
</my-component> Needless to say, @LeaVerou Copying in all styles has had its problems here and there and we've had to find many work-arounds, which would still be the case with |
Apologies. I think I didn't write clearly and it ended up a bit of a word salad. Hopefully I explain myself better here.
In a lot of native controls, such as inputs/selects/etc, there is decent user styling because they are more granular, and the wrapper does a lot of heavy lifting. The more complex, the harder it is obviously to do. (And correct me if I'm wrong, as I've only created a few dozen web components, and you're in this group,) but most people's use cases tend to involve a deeper hierarchy than those.
Right. That's why I was saying 'generally'. Video controls also lacks much customisation in terms of CSS control, nor is there much you would want to inherit naturally. In those regards, my point was meant to be; the ability to turn off authored styling (akin to I like those links, thank you.
I think this would solve some of these problems. But as you said, there will be conflicts still. I like your solution. Did you imagine it being able to cascade at all? I worry about the repetitive nature if you wanted to expose 60% of your component to allow styling. I could see myself allowing at the top level, and then disallowing at certain points.
I worry not allowing granularity (such as classes) may not fix some of the issues. Inputs, Buttons, etc often have |
Some thoughts & questions (adapted from mastodon discussion):
To answer many of these questions, it would help to have a collection of use cases for web components with a mix of slotted and templated elements where styling should be harmonized with the parent document, and examples of how WC authors are currently handling it (by building up templates all in the light DOM, or by copying stylesheets into the shadow DOM, or using complex custom props systems, or ???). |
You've proposed an allow list approach here, but what if there was a block list approach? It would seem that you might be able to achieve that assuming a version An |
I would be happy if I could opt in to external native form element styling by simply setting an attribute on the elements within the shadow root. Something like Another option could be something like a Unrelated, but more important to me - being able to style all aspects of the various input elements. |
Aside from web component authoring perspective, I'd also suggest some affordances from an end-user perspective (as in the user of a web component provided by a library): an API to enable this at runtime. It can be an attribute as suggested by @PaulHMason, for example, This can help me, as a user, to use a web component with global overrides if needed and without if not needed. |
I strongly disagree.
As a WC author, you want your component to Just Work™, without people using it having to jump through hoops. It's far preferable to shift complexity to the component internals, than outwards to the component consumer.
I don't see the point. WIth open shadow roots, authors can always access whatever they want through
That's a good point, and indeed a syntax that allows for this too would be great. Though I worry that trying to cater to those use cases as well would overcomplicate this, or prevent the syntax from catering to other use cases (e.g. one can imagine a syntax where you explicitly specify what element types and attributes you want this to match as, but then you lose combinators etc). I'd also imagine that having a
This is why my proposal was to exclude certain "userland" attributes from matching. We definitely don't want to be maintaining allowlists of attributes in the spec though, that's a recipe for disaster.
Per my original proposal, combinators would match as long as all the elements involved were exposed. E.g.
Ah, that's a good point, right now these are tree scoped, i.e. animations from the outside do not trickle in.
Not sure what you mean in either of these questions.
Good point, I'll try to gather some. FWIW what prompted this was me writing a component for image input (that supported both linking, uploading, pasting etc) and I wished the input and button could automatically get styling from the host page. I think the way most WC libraries deal with it is that they include their own core styles, and try to offer parts and custom properties liberally, for customization. Which of course means a lot of integration effort for the WC consumer. |
My dream for this issue is to have a Then, besides :button, there could also be |
From my recollection of the many discussions about Web Components with some google folks many years ago, there was one idea that hunted me for years, it was @domenic challenging the presumption that web components were meant to be used to encapsulate complex logic, specifically, multiple layers (logical layers - (e.g.: a gallery using a gallery item on its shadow, or a product set, containing a product price on its shadow). He kept saying, "don't do that", and of course, we didn't listen. It took us years, it took me years I should say, to realize what he was saying, and more important, to realize that this is the root of many problems for Salesforce ecosystem of Web Components. If you fall into that trap, then all the problems that you're describing will emerge immediately. My recommendation is: "Listen to @domenic", it is somehow counter intuitive because most of us are coming from different frameworks, and mostly trying to adapt to the WC model, so it is very tempting. Here are some of the rules that you can reasoning about:
I suspect that we, as a community, need to do a better job communicating and perhaps be more prescriptive, certainly implementers will not do it, but I see more and more developers falling for this trap, and at the end it is not helping the WC cause. Trying to supplement WC with new APIs to allow you to customize encapsulated components will never be good enough IMO, because the problem is the model you're aiming for, not necessary the WC API. |
I haven't been able to understand all the proposals or ideas in this thread, but I will briefly say that this is part of something I've been thinking about writing up recently. Which is that, basically, web components were designed from the beginning to give the same styling capabilities as native controls like I think @caridy summarizes this well, but I'll give my own take on what "the same styling capabilities as native controls" means:
This is what led me to invest a lot of time in web component improvements like default accessibility semantics, form-associated custom elements, CSS shadow parts and custom state, etc. (We still haven't figured out default focus though...) In practice, I think this is rarely how we've seen people using web components. (Despite, maybe, @caridy and his team coming around to this point of view? 🙂) People want to use them as a full "component model" in the same style as React and friends. This leads to a bunch of new proposals like declarative shadow DOM, open-stylable shadow roots, scoped custom element registries, deferred upgrades, etc. In my view all such proposals are part of making them better as a React-competitor component model. Where does that leave us? Well, I've come to peace with the idea of building things developers want (a full component model), instead of saying that you're only able to use web components fruitfully if you are building native-HTML-like elements. I still think it's an elegant vision to build out a suite of technologies such that you build new HTML-ish generic elements, with their interesting content in light DOM and their decorations selectively exposed as |
As someone on the app side of things, I can say we have lots of low level web components within our application, and we also have lots of app level web components that to varying degrees are internally composed of lots of other components. We went all in, so we aren't faced with these styling constraints to the same extent, but I understand that it does block adoption for others. More often, we are limited by third-party integrations that have not considered DOM encapsulation by relying on global styles or assuming everything is in the same scope. For a long time people wanted the ability to define their own elements, and web components meets that need in a standard way supported natively across all current major browsers - no special flavour of the month libraries required. Some of those app level components are shared across the app and potentially used in different contexts. All of our teams, partners, or super-users can build for our app provided they're using tech that is compatible with web components, and we don't need to force them to use specific version of a component library that we may or may not want to support tomorrow. The last thing I would want is to use flavour of the month component library for app components. I know React isn't exactly flavour of the month, but we've been bitten by incompatible versions with it nonetheless. So while there are gaps with web components, my hope is that we would continue to strive forward and make it better for building apps. |
@caridy @domenic It's inaccurate (even a bit dismissive) to say that everyone who has been running into these problems is trying to use WC for templating, framework-style. I've been on the side of building WC as native-like HTML elements for a while — these types of components are literally 99% of the components I have ever built — so you are preaching to the choir here. I’ve been pretty vocal about how it was a mistake that style ended up being baked in to WC libraries — instead they should have been blocks of functionality, encapsulating only functional CSS, and entirely themable from the outside (most recent tweet on this). And yet, I've ran into the use cases I'm describing repeatedly, while authoring WC with that mindset. As I mentioned in my first post, built-in components run into these issues as well, we've just accepted that this is how things are supposed to be. For example, think of the "Browse…" button in most or even the arrow buttons in Is the intention really for these to not inherit any Themable from the outside does not mean it's okay if authors have to repeat all their global styles for every single component they add, because it uses different parts for its buttons and inputs. Experimenting with a WC should not take that much work. |
This is what's important. People try to solve a problem and a standard should help them focus on solving the problem rather than ceremony. Too much ceremony nukes the motivation to work with something and leads people to embrace other ways (that's React, Svelte, etc) to solve a problem. |
@LeaVerou there is no question about it, they are limiting, and I don't know exactly why, but I'm not an implementer! I will assume, that if you are implementing a |
If we have a tree of WCs, it feels like it becomes quite a chore to go explicitly re-target all the various "arrow buttons" (or what not) throughout the tree, no? (I may be missing some option!)
This feels like it's going the opposite direction from where your previous post was, which seemed to be warding against making too many fine grained independent components: whoops we accidentally built both It feels like trying to insist each component be designed ahead of time to be fully disassembled, have to be crafted together by a lot of pieces. I actually like a lot of this notion, quite a lot, but I think it would take quite a radical turn & eternal vigilance to disassemble our WC world into small enough atoms. The idea here of components maintaining integrity but also being able to participate on the page they're on sounds like a more interesting twist, and it alleviates pressure on the page to top down define each atom, leaves WCs more able to work bottom up, which sounds like a wonderful convenience for component authors (who don't want to have to assume a blank slate input for every detail) and for page authors (who don't want to have to hand assemble most of the things inside the WC tree). |
Tangential, but it would be great if all of these would just show up in dev tools somehow, even if it meant the author having to enumerate in some way the parts/properties to expose. |
@kektide you are in control of the registry, or you should, if that's the case, then why do you have a |
If authors have to include manual buttons just so that these buttons are styled like the rest of their page, that does not seem like the right abstraction, nor is it particularly maintainable (the buttons may include other attributes that authors must now recreate). Including custom elements via slots should be reserved for extreme customization, not basic usage. Similarly, parts are great, but not a good solution for just applying default, baseline styling. Imagine having to turn your |
::part(button) {
@include $ns-button;
} This becomes opt-in, preserves encapsulation, can be combined with other ideas like having multiple sheets exported by a single document and has user-land benefits beyond just shadow DOM. |
Correct me if I'm wrong, but that would lead us right back to the problem of using components requiring too many extra steps, would it not? The point here is that component authors should be the only ones who have to do anything, and that it shouldn't be too inconvenient. Having to explicitly bend styles to work with component parts really defeats the purpose, specially for more complicated nested components where you'd have to list lots of boilerplate for all the different parts. |
Yeah, I’m tending to agree with @DarkWiiPlayer: what @calebdwilliams is proposing would be slightly better than the current situation, but it has several issues when it comes to addressing the problem statement in the OP:
|
It feels that we are not speaking the same language, or that I'm not understanding very well the motivations for this request. Let me try to provide an example (similar to what I mentioned above), and you can tell us exactly that the problems are: <body>
...plenty of markup... with different levels...
<x-gallery>
<x-gallery-image></x-gallery-image>
</x-gallery>
<x-carousel>
<x-carousel-image src="1.jpg"></x-casousel-image>
<x-carousel-image src="1.jpg"></x-casousel-image>
<x-carousel-image src="1.jpg"></x-casousel-image>
</x-carousel>
...more markup --- all owned by document, hence no #shadowRoots here...
</body> We can assume that:
Now, based on those assumptions, what exactly is what you're asking, and why? |
I think the main point here is that, in an ideal world, a user (who may or may not be very knowledgeable about HTML, CSS or JS) would have to a) link some style sheet with buttons and stuff and b) link some That's the promise of custom elements: you import the JS and put the elements on your page, and most of the complexity is the responsibility of the element author. So it makes more sense to give component authors the tools to handle selectively pulling in external styles they wish to use while preventing others from messing up their components. |
@LeaVerou I think that basic idea (of I can envision something like the following catching on /** Styles within a shadow DOM */
button {
@include from-root(button); /* or */
@include from-root(.btn);
} |
button {
@include from-root(button);
} Would this include a) any style rules from outside that have the selector (also, I still think |
The way I'm thinking about it, something like
|
I probably phrased my question a bit weird. Say you have a document style sheet with In a shadow root you do The reason I think this is important is that selectors can often get a little bit more complex in the real world and, personally, I think it would be a more common use-case to specify a selector and have all matching elements be styled "like outside", instead of white-listing specific selectors that we want to pull in. Say for example I have a list of selectors like I don't see much of a downside to including the This would also allow for more complex selectors like Of course, this assumes that more purpose-specific class names won't usually clash and are given reasonable names, which might not always be the case. But this can easily be circumvented by prefixing classes inside the shadow DOM that should not pull in outside styles like |
The way I’m thinking about that option is that only the styles included get applied so if you want danger you could reapply that to any other selector from the outside in but always an explicit opt in .uhoh {
@include from-root(.danger);
&:hover {
@include from-root(.danger:hover);
}
} |
@caridy I think we probably have different frames of reference. E.g. a gallery is not a very good example, since it's common to want to style its controls differently from the general button styles of the rest of the page. I mentioned several examples in the OP but if you want a more concrete example, here's a more concrete example inspired from the component that finally pushed me to make an issue about this (but I have encountered the issue dozens of times while building many different types of components): Suppose you have an <img-input>
#shadow-root
<style>/* internal styles */</style>
<div id="drop-zone" part="dropzone">
<input id="url" part="input location">
<div id="upload-wrapper">
<input type="file" accept="image/*">
<button part="button browse-button">Browse…</button>
</div>
<img id="preview" part="preview">
</div>
</img-input> with a sample rendering like this: Now, you want that input and that button to be styled like every other input or button in the rest of the page, right?
<img-input>
<input slot="input" />
<button slot="browse">Browse…</button>
</img-input> just so they can use your component, right? Boilerplate repetition is exactly what WC are trying to curb.
|
This comment was marked as off-topic.
This comment was marked as off-topic.
@keithamus I think @LeaVerou means to render those e.g I wouldn't recommend "sprouting" elements in the light DOM, no native elements do this as far as I know. And UI frameworks don't like this at all, a React hydrate pass for example will do a full client re-render if there is a DOM tree mismatch in elements. (attributes are okay I think, just gives a warning) |
Ah yes I understand now. I've hidden my comment as it was made from an incorrect basis of understanding. |
The selector problem isn’t specific to custom elements, is it? A list of selectors including many pseudo-element selectors is needed for describing “all buttons” without any custom elements in play: I don’t think that’s ideal either, but I suspect a solution ought to address the problem generically rather than just for custom elements. I wonder if things would change here if native composite widgets all began exposing their buttons as |
Literally, from my first post:
It's not just about buttons and inputs, it's just that the most common use cases involve these. There are more examples in my first post. |
I find this thread fascinating because it gets into the heart of what the use-cases are for web components, and we can see that there are competing visions for what that is. I tended to side with @caridy and @domenic's idea, of web components being decoration on top of light DOM where the interesting stuff was. The current problem with this vision is that it only works if you are ok with the fallback. As an example: <fancy-input>
<input type="text">
</fancy-input> This works as long as you are fine with regular The fix for this problem would be, the built-in elements need to get better. Probably much better. If that happens then the fallback is not so bad, and so using web components for extra nice-to-have decoration starts making more sense. The other competing vision of web components as essentially "macros" for more DOM has the problem that it's not a portable concept. Web components only run on the client-side, but many sites want/need their content to be included in initial HTML. So then you get into "server-side rendering" which is difficult for a number of reasons, but even if you accept the tradeoffs you are reducing the number of people who can use your components. It has to be those who are only using JavaScript on the backend, and only those who are using a certain framework for which your SSR solution works. So this approach is possible, given you are willing to accept the tradeoffs, but it's no longer portable. And portability is one of the main reasons to want to use a standard. |
Sigh. Not sure if it's worth pointing out, for the Nth time, that the use cases that motivated this thread are not about the model of web components as macros. It's like people are so convinced that there can only be two views on what WC should be able to do, they desperately try to shoehorn any view into one of these. Every time I think I've finally explained the problem statement well (even with a concrete example here), someone else comes along that has misunderstood the entire problem statement, and I’m out of ideas about how else to explain it. 😔
I’m…honestly not sure how this relates to the problem statement. Is the idea that |
@LeaVerou Categorization is a natural thing to do when breaking down a large problem space with many different solutions. If you think I've miscategorized the type of components you are talking about, then what category do you think they belong to? From your example,
The idea is that all important elements should be in the light DOM. That is, something you would consider a requirement for a functional page. If So, as an example, a submit button for a user to buy your product is very important, it should always be in the light DOM or you are risking a chance of not receiving payment. What is less important is up to the individual site. I tend to think everything is important, otherwise why is it on my site in the first place. |
Okay here's an attempt at breaking this down in a reasonable way: All web components have two types of elements:
In the case of a light-dom input box, the "interesting" content is the text-input, which already gets styled from the outside. We can ignore this as it already works. In the case of an image carousel, the interesting content are the images, provided by the HTML author, but the forward/backward control buttons are part of the additional functionality that the component provides. This is what we care about. As has been shown above, putting these in the light-DOM is not optimal, because they're functionality that the component is supposed to provide. This distinction into "user-provided" and "component-provided" functionality and elements seems (to me at least) universal to all the possible applications of web components, even if at a different split. And I really hope that we can now continue to discuss the actual use-case, because if the discussion continues this way there won't ever be an actual resolution. |
This actually touches on a pet peeve (or at least concern) I have, which I wonder if it could be rectified as part of this proposal? First, even implementing this hack would be problematic without this issue being finally resolved. But even if that precondition is resolved, I agree that an element spawning its own light children seems a bit counter to how elements are supposed to behave (where even setting attributes is heavily discouraged). Now it is possible to provide default slot content. I would have expected this to behave like an optional JavaScript parameter -- indistinguishable from having it passed in from the outside. But default slot content doesn't fire the slot changed event, and doesn't automatically inherit the styling one would get if the slot content is provided externally. It is possible to style it, but as far as I know we can't specify it to inherit styles from the parent Shadow DOM realm. I would propose an attribute to make it behave like it was provided externally. I can't think of a good attribute name for describing what I would have hoped is default behavior, but something like: <slot name=file-selector id="upload-wrapper" treatdefaultcontentlikeitwaspassedin>
<input type="file" accept="image/*">
<button part="button browse-button">Browse…</button>
</slot> This would cause all slotchanged events to fire and assignedNodes to be set without using flatten, and inheriting styles. Not sure if this would also resolve all use cases @LeaVerou is thinking of, but maybe some? |
WCCG had their spring F2F in which this was discussed. You can read the full notes of the discussion (#978 (comment)), heading entitled "Theming / open styling". In the meeting, present members of WCCG reached a consensus to discuss further in breakout sessions. I'd like to call out that #1006 is the tracking issue for that breakout, in which this will likely be discussed further. |
I think ::part() is nice and works well, but for big / composed WCs it can be cumbersome to expose and document parts for every element inside a component. For instance: <ul class="list" part-with-descendents="amazing-list">
<li class="list__item">This in an item</li>
<li class="list__item">This in another item <span class="list__banana">with something special</span></li>
</ul> And then: cool-component::part-with-descendents(amazing-list) {
border: 1px solid red;
}
cool-component::part-with-descendents(amazing-list .list__item) {
color: tomato;
}
cool-component::part-with-descendents(amazing-list .list__banana) {
text-transform: uppercase;
} This would allow the WC author to expose whole sections of the component, or maybe even the whole component if they want to. |
@luis-pato that will probably defeat the whole purpose of a controlled styling process, giving consumers of the web component the ability to modify the styles of any internal implementation details. |
@caridy Maybe. But WC authors could expose only those parts they want the devs to be able to modify. Much like the ::part() selector, but also allowing for the children, if that is wanted... |
Well, I completely forgot I had started this thread, and just commented the following in #909 :
|
This originates in this Twitter thread and this Mastodon thread.
Problem statement
Components for encapsulating and packaging up blocks of functionality are usually composed from built-in controls, such as buttons and inputs, often recursively.
Host pages nearly always include base styles for all built-in controls, yet these components cannot take advantage of these styles without placing their elements in light DOM, which breaks encapsulation entirely. Furthermore, there is not even a straightforward way (either in CSS or JS) for these components to pull in styles from the shadow host.
As a result, it is currently a nontrivial problem to create a web component that e.g. contains a text field and a button, and have that text field and button look like the other text fields and buttons on the surrounding page.
Yes, there is
::part()
and custom properties, but this means the host page needs to know which parts and custom properties the component uses, so currently authors need to spend a considerable amount of time integrating a component in their page, which makes it harder to mix and match components, and promotes monolithic component libraries that share the same styling conventions. There have been some efforts to standardize styling conventions, but nothing has caught up.This also affects built-in components as well: e.g.
<input type=file>
has historically been annoying for authors because even though it includes a button, the button does not follow the page's button styles and needs to be styled separately.or even the arrow buttons in
<input type=number>
:This also comes up for non-interactive components: e.g. there are plenty of components for including Markdown in an HTML page, all suffering from the same problems: either they render Markdown in Shadow DOM and thus it looks out of place unless the page author spends a considerable amount of time duplicating their core styles for parts (assuming everything is actually exposed via parts), OR everything is rendered in the light DOM so it can be styled as normal, which means the original Markdown code is lost after the first render.
There is the old
open-stylable
Shadow Roots proposal, but I think for most use cases component authors need more control than indiscriminately pulling in all styles from the host page.Potential solution
Essentially what we need is a declarative way for component authors to selectively opt-in controls to be stylable from the shadow host's styles, with certain restrictions so that you end up essentially only getting global styles intended for these controls (rules like
button
,button:hover
,input[type=number]
,details > summary
,[popover]
), and not random ad hoc CSS code that would cause styling conflicts. I think the following restrictions would achieve that:id
,class
,part
,data-*
(any others?):nth-child()
to match, do we? Unless we want to be able to pull in styles for more complicated structures like entire tables — do we want to cover this type of use case?The opt-in mechanism would likely be an attribute, name TBB. Some (poor) ideas:
hoststylable
,importstyles
,allowhoststyles
. Hopefully we can find something shorter than these. The idea being that the attribute would start as a boolean attribute, but could in the future be expanded to take values for customizing the behavior.Open questions
exportparts
) or do we just define it so that matching styles from all parent shadow hosts apply? It seems like the latter could better preserve intent.I'd love to hear from implementors: would something like this be feasible? How much implementation effort would it require? Are there any changes that would improve implementability?
The text was updated successfully, but these errors were encountered: