-
Notifications
You must be signed in to change notification settings - Fork 382
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
Support Custom Pseudo-elements #300
Comments
At the first glance, the proposal sounds good to me. @tabatkins, WDYT if you have a chance to take a look? |
We would prefer using <date-range-selector>
<!-- #shadow-root -->
<div id="container">
<input part="start-date" id="start-date" type="date">
<input part="end-date" id="end-date" type="date">
</div>
<!-- /shadow-root -->
</date-range-selector> date-range-selector:part(start-date),
date-range-selector:part(end-date) {
/* normal styles */
} |
Yeah, if we do add custom pseudo-elements, we need to do it namespaced, like That said, the CSSWG provisionally accepted my @apply rule draft, which makes pseudo-elements unnecessary for WC. That is, CSS variables handle most of the styling needs of WC already. They only fall down when you want to offer the ability to arbitrarily style an element; there are too many properties to make it reasonable to offer enough variables for styling. Pseudo-elements expose an element for arbitrary styling, so that's useful. The @apply rule also does that, tho, and via the existing mechanism of CSS custom properties, so there's probably no need to add custom pseudo-elements. |
@tabatkins I'm aware of the However, I brought up several use-cases in the proposal that I think are legitimate and that I don't think the You said:
Can you go into more detail here? How would the input {
/* normal input styles */
@apply --date-range-selector-inputs;
}
input:focus:enabled:out-of-range {
/* input styles for the focus, enabled, and out-of-range states */
@apply --date-range-selector-inputs-focused-enabled-out-of-range;
} It seems unreasonable for component authors to have to anticipate and hard-code every element state their users may ever want to style. Exposing the element itself is much simpler and still maintains the privacy of other element in the component (unlike |
You're right, this doesn't handle pseudo-classes well. I'll have to give that some thought. |
I'm just a user, but, in my opinion, there is a need for pseudo-element functionality from a perspective of allowing a component author to provide a stable public interface for styling a component. As a component author, I should be able to change the internal implementation details of my web component without any disruption to a component user. The only way I can feasibly do that is to have an explicit mechanism for defining my publicly style-able surface. Custom pseudo elements do this. While Vendor elements that utilize obscured internals have long allowed styling of certain portions via pseudo elements. And, correct me if I'm wrong, isn't there some work on standardizing these for common replaced elements, like form controls? Therefore, with custom pseudo elements, a user authored component would be style-able like a vendor component. Having user authored elements feel like native elements is a good thing--and not only because having the standard explain the existing platform is nice. A component user does not have to learn any new language constructs to style a custom pseudo element, and yet can still enhance their use of a custom pseudo element with new CSS features as they develop. While I am enthusiastic about bringing popular pre-processor features (and implied enhancements, like |
Efforts to standardize the internal pseudo-elements aren't very successful, unfortunately. I keep trying to get something done with that, but it's very difficult to do in a reasonable way. I'm not confident that it will ever be achieved. Mixins and Nesting are battle-tested concepts with long histories from CSS preprocessors; their mixture, especially, has a long history of use in Sass (and maybe more?). These aren't new concepts, they're just new to vanilla CSS. And they lean on existing useful and well-tested concepts from CSS, like inheritance and properties. Custom pseudo-elements, on the other hand, don't. We've had pseudo-elements for a long time, but only a small number, and they don't nest. We still need to figure out how to solve a number of issues before it's usable:
All of these questions apply to vars/@apply/nesting too, but they all have definite, simple answers there, and those answers are imo reasonable behavior. We have no idea what the answers would be for pseudo-elements, tho. This is why it's backwards to characterize vars/@apply/nesting as "new and untested" and pseudo-elements as "old and well-known". It's almost exactly the opposite when you dig into details. |
I imagined them as being non-unique, like classes. It's extremely likely that component authors will create elements with many children of the same type, like a Though that raises the question (at least in my mind) of whether or not to allow a single HTML element to have more than one pseudo element name. To continue with the
I think this paradigm can be simplified to just components and their sub-components, rather than thinking of it in terms of a possibly infinitely nested tree of shadow components (ala <x-foo>
<!-- #shadow-root -->
<x-bar pseudo="x-bar-item">
<!-- /shadow-root -->
</x-foo> If x-foo::x-bar-item::x-baz-item {
color: red;
} This paradigm allows for |
As I was writing that last post, I thought of a possible solution to the pseudo-class/ Here's what it could look like:
This, if There are definitely some specificity/cascade issues to iron out, but if this could work, then I believe all my concerns about custom property shortcomings would be alleviated. |
Like we agreed during the last F2F, custom properties, In particular, in an isolated web component case where neither component nor its container document trusts each other, we can't expose all mixins defined in the container document into the shadow tree. Also, there is an added benefit of developer familiarity with custom pseudo elements. While the technical problems faced by nested rules and |
Also, where is the proposal for nesting syntax? Is it https://lists.w3.org/Archives/Public/www-style/2011Jun/0022.html ? |
I think custom pseudos are a good direction. Comments on some specific issues: (1) It's critical for component authors to be able to whitelist the set of style properties that can be applied to an exposed named part. |
@philipwalton (re: nesting) Yes, that's precisely what I was getting at when I was talking about
No, they don't address slightly different use-cases. They address the exact same use-cases.
Sure we can. And when you do want to block, that's what
I already addressed this in my earlier reply to Rachel.
Why? CSS doesn't do this today. ::before/after take all properties. ::first-line and friends don't, but that's because they're not actually elements, they're bizarre collections of fragments (to use CSS terms), and that limits what you can reasonably do with them. That's not relevant to this topic, tho; we're only planning to expose actual elements here. If you want to whitelist properties, that's best done with simple CSS variables - expose variables for each property you want to allow. If you want to expose arbitrary styling but blacklist some properties for sanity, that's trivially done with @apply - just list the blacklist properties with their desired values after the @apply rule. With custom pseudos, this is yet another new thing you'll have to define and introduce, presumably in JS.
Yes,
It's not "increasing", it's complete. Variables/@apply/nesting are a complete styling feature. The syntax is relatively new, but a lack of immediately familiarity doesn't automatically imply "inscrutable". It's a declaration block stuffed into a custom property. Nesting is familiar to users of every single CSS preprocessor in existence, and is consistently one of the most popular and highly used features. The learning curve here is miniscule based on practical evidence. |
That's such a cumbersome API. So if an author wanted to use a component and didn't want to expose any custom properties to it, then he/she has to do
I disagree. Nesting declarations of pseudo-class selectors inside custom mixins is extremely confusing. |
@rniwa, what would the custom pseudo-element equivalent be for whitelisting the styleable properties? As the proposal is now (and obviously it can change), exposing an element as a custom pseudo element exposes all properties. Presumably component authors could use |
@tabatkins, yeah I think you're right. I'd originally imagined a situation where element-author-defined pseudo-class rules would trump |
@philipwalton : My point in #300 (comment) is nothing to do with whitelisting properties but more to do with exposing mixins and properties across component boundaries. In the case of custom pseudo elements, the authors of components are explicitly opting in the contract that a part of its component is stylable by its user, and the users of components are similarly opting in to style those parts without exposing any other mixin or custom property defined in the document. This is a crucial property for the isolated components where neither component author nor component user trust each other (e.g. for cross-origin widgets). |
I'm not seeing the complexity. Did you actually write it out and see what it would look like, compared to whatever else you'd want to do? /* set one variable for a sub-component,
letting the outer page set the rest if they want */
sub-component {
--heading: { color: blue; text-decoration: underline; };
}
/* set one variable for a sub-component,
and force the rest to be default value,
blocking the outer page from setting anything */
sub-component {
--: initial;
--heading: { color: blue; text-decoration: underline; };
} I'm not seeing the "too much complexity". And note, we're comparing this to a nonexistent, unknown syntax for exposing or hiding the ::part()s of sub-components. We have no idea if such a syntax will be blacklist or whitelist based, how it will be invoked, or what language we'll use for it. (HTML attributes? JS api? CSS syntax?) We have no clue what its complexity will be, because it doesn't exist yet. This is one of many already-mentioned unanswered-so-far questions about the ::part syntax.
You're free to disagree, but heavy usage of nesting in Sass and others goes against your feelings. I'm not sure what the usage numbers are for the nearest direct analogue (Sass nesting within sub-component {
--heading: {
color: blue;
text-decoration: underline;
&:hover { color: red; font-weight: bold; }
};
} Compare this to an assumed ::part-based syntax: sub-component::part(heading) {
color: blue;
text-decoration: underline;
}
sub-component::part(heading):hover {
color: red; font-weight: bold;
} Those look basically identical. I won't fault ::part() for the extra selector verbosity there; if Nesting exists, it can be simplified to a basically identical form: sub-component::part(heading) {
color: blue;
text-decoration: underline;
&:hover { color: red; font-weight: bold; }
} I just want to emphasize, again, that ::part() has a number of currently-unanswered questions about its functionality and syntax. I outlined several of them up above in a previous comment. They certainly can be addressed, but haven't been so far, and when they are, several of them will imply further syntax and complexity for the feature. Custom properties and @apply/Nesting, on the other hand, have answers to all those questions right now; they fall out of the definitions of the feature. For more complex cases, until we actually answer the relevant questions for ::part (such as how sub-component ::parts are exposed or hidden), we won't have the ability to do good comparisons with anything else. |
Fully isolated components are a trivial case in the vars/apply/nesting feature set; you just don't use any undefined variables. If you only use variables you define yourself, there's no way for outside-world inheritance to interfere. I presume this is the same as in ::part - just don't expose any parts, and nobody can style you. If you further want to block sub-components from receiving styling, this is trivial in vars/apply/nesting, as I demonstrated above (or if you want to block everything immediately, for all your sub-components, just apply a Since we have no idea how ::part will expose/hide the parts of sub-components, we can't make a proper comparison yet, but we can at least see that the v/a/n option is trivial. |
Well, the problem is that we do need the fidelity of being able to expose some parts of the isolated component. This is the exact use case addressed by various builtin pseudo elements in WebKit. Since that is a feature that has been shipping in WebKit/Blink for years and proven to be popular amongst developers, I don't see why we need to re-invent the wheel to replace that solution. |
Yes, I understand that we need to expose particular parts for styling; that's addressed equally well with v/a/n or ::part. (With v/a/n, you document several variables, and use each within an appropriate selector, like What, specifically, do you think is hard with one or the other solution?
No matter what we do, we're going to be reinventing things. We will not produce a solution that allows for a 100% fidelity recreation of the current form pseudo-element stuff, because that immediately runs into namespace issues. Current form controls also only touch on a small subset of the possibility space, while ::part() needs to address a bunch more cases (I outlined some in a previous comment). So lots of invention have to happen no matter what. |
From my point of view, custom elements are here to give authors the same power as user-agents have of defining elements, or at least as close to that as possible. The elements defined by the spec are not necessarily implemented with JavaScript and CSS, but they could be. Even the most convoluted ones like Giving authors the ability to create custom elements is putting them at nearly the same level as the user-agent, allowing them to define elements in the same way the user-agent does. Not without giving authors the power to create custom pseudo-elements feels incomplete to me, as it’s yet another thing the user-agent can do that the author cannot. By giving authors the ability to create custom pseudo-elements you are bringing their power closer to the user-agent’s. @tabatkins effectively, what I’m saying is that I prefer custom pseudo-elements to your suggestion because it is closer to what is done by user-agents to regular elements. I suggest the following syntax: x-foo..custom-element
{
/* styles */
} As it’s comparable to what we have today: /* user-agent-defined class */
button:enabled
{}
/* author-defined class */
button.loading
{}
/* user-agent-defined pseudo-element */
x-foo::first-line
{}
/* author-defined pseudo-element */
x-foo..last-letter
{} |
That comparison doesn't really hold true for web components. "User-agent-defined [classes]" (pseudo-classes) represent the target when it is in a particular state without modifying the attributes of the element. Component authors have a requirement similar to this, as opposed to page authors who are using the component. Page authors expect that, when the internal state of an element changes (e.g. focus), its attributes do not (though its properties may). A component author, therefore, wouldn't modify the class attribute of the custom element hosting its shadow tree. In a similar vein, a component author might wish to define a "pseudo-element" to represent an internal part of the component, which they would do without modifying the host element in any observable manner. A page author would just add a child element to the host. /* user-agent-defined state */
button:enabled
/* page author-defined state */
button.loading
/* component author-defined state */
x-button<???>
/* user-agent-defined pseudo-element */
foo::first-line
/* page author-defined element */
foo bar
/* component author-defined shady-element */
x-foo<???> If you want to propose new syntax, you should rethink your premise for choosing |
Hrn, you’re right. Either way, my point is: I really dislike the But does anyone disagree with what I said about custom pseudo-elements?
Addressing #300 (comment): (1) Are shadow-pseudos more like classes or IDs? I think they should act exactly like ids. invalid: <div pseudo="foo bar"></div> invalid: <div pseudo="foo"></div>
<div pseudo="foo"></div> (2) How do we handle both a parent and child exposing themselves as pseudo-elements? <div pseudo="foo">
<div pseudo="bar"></div>
</div> /* matches nothing */
x-baz::foo > div
{}
/* matches nothing */
div > x-baz::bar
{}
/* matches nothing */
x-bar::foo::bar
{}
/* matches <div pseudo="bar"> */
x-baz::foo > x-baz::bar
{} (3) If a component contains other components, and wants to expose some of its sub-components parts as pseudo-elements, how does it surface them? You can access them by nesting <!doctype html>
<html>
<head>
<style>
x-foo::bar::baz::before
{
content: "Hello, world!";
}
</style>
</head>
<body>
<x-foo>
#shadow
<x-bar pseudo="bar">
#shadow
<button pseudo="baz"></button>
/#shadow
</x-bar>
/#shadow
</x-foo>
</body>
</html> |
How would you "forward" a part, I don't quite understand that. |
Say you're using an |
Can you only forward a single part, then? |
No, the |
True, but I still think there's an argument to be made for theming with Without |
I may not understand @apply very well. But it seems like @apply puts control in the hands of component authors for where stuff gets applied, and the expected API contract is that the client of the component provides some custom properties with the intended styles. But @theme applies style to all parts with a given name everywhere. And the component client has to decide whether to use @part or @theme. So the component's API contract is not just a specific name of the styling hook, but also the requirement to know whether they should use ::part or ::theme with it. Meanwhile, forwarding provides a completely component-controlled way of handling it where the client should always just say ::part. Components authored with a closed shadow DOM will have to be done with forwarding, and in that case ::theme is useless. So different kinds of components will have different API contracts for how to style them that are dependent on an authoring choice that shouldn't be relevant to this. In brief, it seems like telling your users to use ::theme (or leaving the choice to them) is poor authoring practice, and explicitly forwarding part names is good practice. The fact that ::theme theoretically is similar in power in some sense to @apply is not a good reason to have it. Depth of styling should be controlled by the component, not the client of the component, and leaving it to client code adds a needless confusing decision. |
Not really. Like I said above,
If we all collectively decided that we only wanted Ultimately, if you can pass a single value arbitrarily far down the flat tree without the intervening elements having to do anything (besides just fail to stop you), it seems weird to not allow sets of values; disallowing it doesn't actually add any security whatsoever, just makes it less convenient for component authors and users.
Just to reiterate, it's not similar "in some sense" - it's exactly as powerful in a direct-translation sense - |
"exactly as powerful" is a misleading statement given Having said that, we're all for focusing on |
I disagree that
|
I'm not discounting or denying the importance of use cases for |
One thing I’ve been wondering with the new “custom shadow parts” proposal is how do we expose the host element for theming by default? Can we add a As a simple use case, if I have a button component I want to provide themes for, which are not bundled with the component, and I’m not the author of the button component. The component has only the host element, no extra elements/parts in shadow DOM. So, I would like to offer users an additional stylesheet they can load, which would give a new default look for the button, but also offer some extra styles/variations for it, like “small”, “large” and “primary”. <link rel="stylesheet" href="theme-for-nice-button.html">
<!-- This would now look different than the <nice-button> component looks without the theme -->
<nice-button>Button</button>
<!-- I would like to offer these kind of additional styles as well -->
<nice-button class="small primary">Small Primary Button</nice-button> Could the <nice-button part="nice-button">Button</button>
<nice-button part="nice-button" class="small primary">Small Primary Button</nice-button> And I could then write the following CSS in the theme I provide: html::theme(nice-button) {
/* My new default styles for nice-button */
}
html::theme(nice-button).primary {
/* Additional styles for the primary button */
} Am I completely off? Has this use case been considered in the “custom shadow parts” proposal? |
No, that's a use case for |
Alright. Do we have that thread already somewhere? |
It was discussed at TPAC2017, the minutes are at https://www.w3.org/2017/11/10-webplat-minutes.html#item03 |
It seems this specification is still hosted in @tabatkins's private GitHub, despite there being feedback tracked here as well: https://github.com/w3c/csswg-drafts/issues?q=is%3Aissue+is%3Aopen+css-shadow-parts. Will the CSS WG adopt this soonish? Can we close this issue as this is now mostly a CSS WG matter? |
Again, we're strongly in favor of having this feature. We have a strong interest in implementing this feature in WebKit as well. |
Not sure if this is the right place, but as a custom elements author, I'd like a way to define toggle EDIT: made a new issue for it: #738 |
No, I just haven't marked my personal copy as being obsolete. The spec is at https://drafts.csswg.org/css-shadow-parts/ and is officially tracked by the CSSWG. |
Let's close this then. I have updated https://github.com/w3c/webcomponents/blob/gh-pages/README.md to point out that proposal so we still have a centralized place to look at for where the various web component bits ended up. |
See the proposal from @philipwalton.
https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Custom-Pseudo-Elements.md
Let me file an issue here to discuss and keep track of the proposal.
The text was updated successfully, but these errors were encountered: