WebKit / standards-positions

WebKit's positions on emerging web specifications
https://webkit.org/standards-positions/
241 stars 19 forks source link

Customized built-in elements #97

Closed annevk closed 1 year ago

annevk commented 1 year ago

Request for position on an emerging web specification

Information about the spec

Bugs tracking this feature

Anything else we need to know

WebKit supports autonomous custom elements, but hasn't implemented this aspect of custom elements.

Colleagues and I wanted to share our thinking around that in this issue and also open it up for feedback from the community.

trusktr commented 1 year ago

@colepeters element-behaviors and custom-attributes both solve the problem you mentioned:

Here's an example with element-behaviors:

<picture has="my-picture" src="...">
  <source has="my-source" media="..."></source>
</picture>

Here's an example with custom-attributes:

<picture my-picture src="...">
  <source my-source media="..."></source>
</picture>

And! Solved without the problems customized built-ins has. Plus these are flexible, and allow "mixing" multiple features:

Multiple element behaviors:

<picture has="my-picture something-else" src="...">
  <source has="my-source something-else" media="..."></source>
</picture>

Multiple custom attributes:

<picture my-picture something-else src="...">
  <source my-source something-else media="..."></source>
</picture>
trusktr commented 1 year ago

does that mean builtin can't benefit because builtin don't have Custom Elements' machinery?

Yes, it is easy to add built-in extends on CE machinery. But the API shape is bad, and it relies on class extension when really extension isn't required (see examples in previous comment which do not require extending element classes).

bahrus commented 1 year ago

@trusktr , please read my document. With all due respect, I think you are missing something.

trusktr commented 1 year ago

I am. Feedback appreciated.

Nice, yep, that's basically like custom-attributes and element-behaviors. "enhancements" is a nice alternative name too. Hmmm! 🤔 I had this same naming conflict with "behavior".

We could also reword anything so that "behaviors" makes sense: f.e. "an element renders in blue" instead of "an element is blue", although "is" is a verb, and technically can be a "behavior". Both are better than "traits" (I just don't like the word).

Both are better than "component". These behaviors/enhancements/attributes are like the "components" in entity/component/system patterns, but without the system. But "component" is far too overloaded everywhere: "web components" (React? Vue? Or Custom Elements?), react components, vue components, etc.

bahrus commented 1 year ago

Yes, but if I suggested "be-" ("be an element that renders in blue") as the preferred prefix, all my elements would start with be-be- which looks a little strange (but I'm open to it).

That would mean changing "enhancements" to "behaviors" everywhere. Again, open to it. Thanks for the suggestion. I'll add your comment. Gratified to know that I connected with someone.

trusktr commented 1 year ago

I didn't suggest double-prefixing like that, I definitely wouldn't want that. Let's leave the conversation there so people can decide on the more important point: can we instead have a better alternative? Someone just needs to officially write it. Nice to connect as well!

colepeters commented 1 year ago

@trusktr Thanks, yeah the example with custom attributes seems like it could be a fit. Intriguing!

Are you aware of any discussion of this approach amongst the W3C or browser vendors, though? Third party alternatives/proposals are interesting, but until they're part of the web platform, we're at something of a stalemate.

This is the thing I'm struggling with the most — we have a viable, standards based option for extending built ins right now in all other browsers, but not Webkit, and it's really holding us back (and again, I understand some of the drawbacks that Webkit and others have mentioned). I'd really love for Webkit to treat this as a priority, because the more time we thrash around with third party implementations, the further we get from evolving the actual standards, which is the whole point of specs like custom built ins to begin with.

jens-struct commented 1 year ago

However, I think it's fair to say that there's no proposed alternative here because Webkit's stance is an exception to the spec which is already implemented in every other modern browser. We can do what we need to do everywhere else, right now, but not in Webkit. I haven't yet seen a proposal from Webkit that addresses issues like strict element hierarchy (and there lots of cases where this is critical). If Webkit cannot themselves provide a working alternative that all other browsers can agree on, it seems to me the right thing to do is for them to honour the spec by implementing it, rather than leaving us to figure it out with third parties, polyfills, etc.

This sums it up very well, if you wonder why things "went a bit off topic".

Alternatives like element-behaviors and custom-attributes

But these alternatives seem to be also very relevant, for at least the progressive enhancement usecases, i can think of.

For me the underestimated major feature of custom elements is the registry, which allows automatic initialisation, when new custom elements are dynamically added to the dom, which are already registered, besides other advantages.

trusktr commented 1 year ago

until they're part of the web platform, we're at something of a stalemate.

@colepeters Yeah, I've only ideated and implemented the idea. Someone would have to make the actual proposal. Maybe we can start one at WICG?


For the purpose of this repo, perhaps a new position like request for alternative, or something, could help. It would signal intent that a solution is wanted, but still not willing to accept the current proposal, and could actively signal for people to submit proposals somewhere.

colepeters commented 1 year ago

@jens-t Thanks, I appreciate it.

I think it's great that Webkit opened this issue up to, as they said, solicit feedback from the community. But we're now coming up on 5 months with lots of community feedback and seemingly zero action from Webkit (though I acknowledge there are a lot of threads going on in this issue).

At some point, though, we need Webkit to actually follow up on this. I get that there are a lot of priorities at play for any browser vendor, and managing all of those must be challenging, so I don't want to sound callous, but I think we all deserve some reflection and considered feedback from the Webkit team at this point.

colepeters commented 1 year ago

@trusktr I could certainly get behind backing a proposal! What I'd really love, though, is (at the risk of repeating my previous comment) for Webkit to put some muscle behind this. If there are legitimate concerns with extending built ins, then the solution IMO is for Webkit to actually propose an alternative that allows us to accomplish what we can already in all other browsers, in a way that gets around those concerns. Right now, it really feels like this is just being ignored, which is pretty frustrating when we have working solutions in all other clients. Again, I'm not presuming any ill intent and I don't want to sound prickly, I'd just really love to see some traction from Webkit here :)

WebReflection commented 1 year ago

This whole discussion point is to have WebKit people chime in and answer or provide alternatives.

Someone just needs to officially write it.

When I say no to something I usually provide a yes to something else ... where is this yes to something else or where is the alternative to the current no from the WebKit team?

'cause I feel like we can discuss this forever but everything will be off topic as the topic here is:

Request for position on an emerging web specification

which is also misleading as that's not emerging, that's an already shipped spec all other browsers implement but we have crickets from the questioned team here ... can anyone from WebKit please share any thoughts around this topic, so we can stop discussing off-topic home-made solutions?

Thank you!

edit I read this too late but ... exactly this, everything else is noise or yet another non standard-track library (check nonchalance too, it's serving me very well getting around all these discussions and using just ECMAScript as solution where nobody, not even WebKit, can say no or it shouldn't work like that ... yet it does).

pjhanzlik commented 1 year ago

Personally, I want to use customized built-in elements to append appropriate child elements when constructed. For instance, copyright content to a footer or user specific options to a select element. You can use querySelectorAll on class attributes and then setup a MutationObserver to get this functionality, but I feel like the point of the Custom Element API to help people avoid making mistakes with global changes. As an example, the CustomElementRegistry prevents you from reusing names or constructors. (Reusing names can lead to unexpected behavior from two global changes interacting. Reusing constructors means you are making duplicate custom elements accidentally.)

I get that custom built-in elements are niche, and that more flexible solutions are possible. I hope Firefox supports ARIA on ElementInternals eventually, and know that a "has" attribute would be better from a dev perspective. That said, I find myself often thinking "Custom built-ins would be a nice solution to this problem", so maybe it is a nice niche?

RobM-ADP commented 1 year ago

A really great use case for this would be for converting links with anchors into ones that use the pushState. For example:

<a is="push-state-link" href="#/some/path">link</a>

An implementation from years ago can be found here: https://github.com/erikringsmuth/pushstate-anchor/tree/master

This is particularly useful for micro front ends that may not know the client side routing strategy used by its host application. This approach is far nicer than implementing a custom-element that either mimics a link or contains a link in its shadow dom.

clshortfuse commented 1 year ago

To explain why I've been commenting about the issues I've been facing isn't really meant to an "off-topic" distraction to Webkit's position. It's meant to describe what we, as devs trying to implement CEs, are dealing with (pain-points). But, I guess it's easier if I just compile the list of what I feel I'm blocked by:

Problem Resolution(s) Fixed
CE cannot interact with forms Form-assosciated Yes
CE buttons cannot be used as a submit https://github.com/WICG/webcomponents/issues/814 No
Shadowed <input> elements cannot use slots for AX labels https://bugs.webkit.org/show_bug.cgi?id=254934 Yes
CE elements can't directly use [aria-labelledby] (customized HTMLInputElement) Cross-root ARIA ; AOM No
href links must be within shadowRoot complicating handling (customized HTMLAnchorElement) Navigation API No
:disabled cannot be passed down a tree (customized HTMLFieldSetElement) No
Some elements must be immediate children (eg: <td> <li> <track> <source>) element-behaviors ; custom-attributes No
CE cannot implement index setters (customized HTMLSelectElement[index]) No
Cross-root complexity with top-level modals (customized HTMLDialogElement) Popover API No

I would rather Webkit implement Customized built-ins instead of targeting all these individual issues piece by piece.

My concern is a lot of these APIs (most), don't have a position on the Kanban so I'm kinda up in the air if CE will even be feasible for full-scale projects in the near future. I'm nearing the end of the component phase, and onto layout + routing next.

I sometimes wonder if moving to CE was worth it and if I should just just write a CE => LightDOM pass for all my components. It was supposed to be easier to work with CE. I do have my hacky, ad-hoc solutions, and they work. Most of it involves cloning nodes (eg: button as submit, combobox popup being top-level and still accessible). My current concern is the amount of code I'll have to write for CE-based, Single-Page-Apps without the Navigation API.

raegen commented 1 year ago

Race conditions do not exist in js because it runs on a single thread? Race conditions are not a consequence of threads, but concurrency. You do know that javascript supports asynchronous execution? :D

WebReflection commented 1 year ago

beside the irrelevant reply for one post from last year, you should've read further:

race conditions as you mentioned there, are solved by customElements.whenDefined which method exists for that exactly purpose: avoid kicking in stuff before it's needed.

can we go back to the topic now? Thank you.

pookage commented 1 year ago

It's kind of embarrassing that WebKit are still throwing their toys out of the pram over this - it's in the spec; I'd love to hear that it's on the to-do list to implement so that Safari isn't just eternally lagging behind everything!

steveblue commented 1 year ago

Given the history of WebKit blocking this specification due to reservations about implementing it and the lack of response in this thread from WebKit representatives, I am less than optimistic customized built-ins will make it into Safari.

I'd like to be optimistic, but instances of WebKit blocking spec and lack of support to fix reported bugs have tarnished my impression of the browser engine. Adding customized built-ins to WebKit would go a long way to restoring the faith I once had and would demonstrate to the entire web development community WebKit is changing for the better. Change my mind WebKit.

inopinatus commented 1 year ago

I use customized built-in elements because autonomous custom elements cannot substitute all parts of the content model.

The consequences of Webkit's blockade of this feature are UI bugs and janky workarounds.

Specific example: https://github.com/hotwired/turbo/pull/131 for the Hotwire framework, a PR which has essentially blocked on Webkit for two years.

anarh commented 1 year ago

Hi WebKit team @rniwa and @annevk, please let us know if you have any updates on this topic. Even if to say, you are having internal discussions. Especially in the face of all the use cases provided in this thread and full support by the rest of the browsers.

Even if it’s not a perfect spec, it would not be the first time browsers implemented and shipped a less than ideal specification.

Please consider implementing this spec and/or proposing a superior alternative specification. I lead the Design System engineering team at the company I currently work at (see bio) and we collaborate well with you on other initiatives. However the silence on this matter in the face of new data from the community is rather uncharacteristic and unusual.

Let’s seriously look into interoperability even when solutions are less than ideal (of which there are a dime a dozen).

EisenbergEffect commented 1 year ago

Personally, I would prefer to have "custom attributes" as described at TPAC last year, instead of custom built-ins. Custom attributes would enable all the same scenarios plus more, would be more composable, and would enable mixing in more than one behavior per element without affecting the inheritance hierarchy.

For example:

<button material-ui ripple-effect="strong">Click Me</button>

In the above example material-ui applies the Material Design System to the button. Independently, ripple-effect applies the behavior that causes the visual ripple when clicked.

The basic strawman API is summed up in this code snippet:

 class RippleEffect extends Attr {
    // ownerElement inherited from Attr
    // name inherited from Attr
    // value inherited from Attr
    // ...
    // there should be a way to add adopted style sheets to the ownerElement 
    // needs API design...

    connectedCallback () {
      // called when the ownerElement is connected to the DOM
    }

    disconnectedCallback () {
      // called when the ownerElement is disconnected from the DOM
    }

    attributeChangedCallback() {
      // called when the value property of this attribute changes
    }
}

customAttributes.define("ripple-effect", RippleEffect);

@rniwa and @annevk Would you all consider something like this as a reasonable alternative?

WebReflection commented 1 year ago

@EisenbergEffect an attribute can exist without an owner element and can be removed as node and re-attached as node later on ... your proposal is missing attributeAttachedCallback and attributeDetachedCallback if it's a serious proposal, imho.

EisenbergEffect commented 1 year ago

@WebReflection We could add that or an ownerChangedCallback but those callbacks would be reflected via calls to attributeChangedCallback() already. However, I don't want to debate the details here. I'm mostly interested if @rniwa and @annevk would be interested in pursuing this composition idea as an alternative to the inheritance-based built-ins. If so, then we can put together a more official proposal.

clshortfuse commented 1 year ago

I think we're conflating two things:

And it's really hard to find what Apple's actual objections to it are. We're all just guessing, so I decided to search for an actual answer.


The irony is I did find an older position saying it's lacking developer interest (which we now have), and out of concern for accessibility. The irony lies in the fact that the lack of Customized built-in requires more work in order to make things accessible as I specified before. In fact, some points, specifically the HTMLSelectElement were brought up almost 10 years ago why developers like myself would want Customized Built-in Elements.

# [03:08] \ i definitely think that attaching shadow DOM to form controls is an important use case. It might be part of one solution for styling form controls # [03:08] \ which is a sorely needed feature # [03:09] \ tkent-san has been working on supporting shadow dom for built-in element, such as input elements in blink. # [03:09] \ sicking: isn't that decorator though # [03:11] \ we thought that attaching Shadow DOM to built-in elements is important use case, but it turned out there is no much demands from developers. # [03:11] \ We can't restrict it to custom elements without making shadow dom and custom elements tightly coupled # [03:12] \ esprehn, hayato: we can add some optional argument in document.register to create a shadow DOM, right? # [03:12] \ For example a browser may implement support for one before the other, shipping shadow dom shouldn't be blocked on custom elements # [03:13] \ esprehn: but nobody can ship shadow DOM because there is no spec. # [03:13] \ It had a spec, what do you mean? # [03:13] \ esprehn: attaching shadow DOM on builtin elements is undefined as is # [03:13] \ It is not undefined # [03:13] \ No more than attaching it to a div or a span, you mean specific things like input # [03:14] \<hayato> rniwa: http://w3c.github.io/webcomponents/spec/shadow/#html-elements-and-their-shadow-trees is trying to address that. # [03:14] \ esprehn: input element is a builtin element # [03:14] \ So is div, but shadow dom works fine with it # [03:14] \ esprehn: for the spec to have a defined behavior for builtin elements, it needs to define behaviors of attaching shadow DOM on every single element we have # [03:14] \ esprehn: including SVG and MathML elements # [03:15] \ Or it could allow it only only div, span, td, etc for now and we could expand it later # [03:15] \ esprehn: that'll be fine # [03:15] \ esprehn: I think the problem here is that we can't move the spec. forward because there are too many edge cases to consider # [03:15] \ Restricting to only custom elements is too restrictive # [03:15] \ esprehn: maybe. # [03:15] \ esprehn: but what are use cases of using shadow DOM for other elements than custom elements? # [03:16] \ Wanting something special on a \ # [03:17] \ esprehn: why don't you just put an element inside td then? # [03:17] \ I guess if you could \ as custom # [03:17] \ That argument does not generalize # [03:17] \ We could eliminate all features :) # [03:18] \ esprehn: as I understand it, we've punted declarative syntax for custom elements for v2 # [03:18] \ Yes # [03:18] \ esprehn: then I don't see why we want to address that use case in shadow DOM v1 # [03:18] \ Huh? # [03:18] \ esprehn: \ # [03:18] \ rniwa: decorators would just be a way of attaching a shadow DOM. The actual rendering should still be done using shadow DOM # [03:18] \ sicking: sure # [03:18] \ Declarative custom elements and type extensions are totally unrelated # [03:18] \ sicking: but as I understand it, we haven't spec'ed that anywhere, right? # [03:19] \ sicking: so it seems natural for attaching shadow DOM to builtin elements and decorator to be spec'ed at the same time # [03:19] \ rniwa: and in the meantime, being able to attach a shadow DOM to a form control, or a header/table/whatever is a great way of working around the lack of decorators # [03:19] \ Not having declarative syntax has no bearing on td is=foo # [03:19] \ rniwa: maybe # [03:20] \ rniwa: (the "maybe" being a reply to your latest comment) # [03:20] \ sicking: that's a good point. # [03:21] \ sicking: I'm not convinced that shadow DOM is the right way to address the problem of styling form controls though # [03:21] \ If we're concerned with complex widgets I'm fine with a whitelist # [03:21] \ sicking: it seems like we want something like what webkit exposes for form controls spec'ed # [03:21] \ rniwa: what's that? # [03:21] \ Although the current idea (and what we implement) is that adding a ShadowRoot makes the native behavior go away # [03:21] \ rniwa: see also my latest email to WHATWG # [03:22] \ sicking: these: http://trac.webkit.org/wiki/Styling%20Form%20Controls # [03:22] \ It turns into a \

effectively # [03:24] \ rniwa: yeah, something in that general direction. # [03:25] \ sicking: I think spec'ing these will be better than having authors replace builtin form controls # [03:25] \ sicking: and then can provide some form serialization mechanism # [03:25] \ Authors already replace the builtins # [03:25] \ The pseudo elements are not flexible enough # [03:26] \ rniwa: authors need to be able to fully replace the styling though. So that you can turn a checkbox into a switch, or a \ is indeed a select # [03:30] \ sicking: that is true although you can just use ARIA role for that. # [03:30] \ rniwa: AT interacts with the DOM # [03:30] \ sicking: not really in webkit. # [03:30] \ rniwa: AT should interact with the HTMLSelectElement even if there's a shadow DOM attached. # [03:31] \ Shadow dom just changes the presentation # [03:31] \ yup

Here we are ~10 years laters manifesting the reasons discussed. Still the reason given here is that it lacks spec. But now it exists. If the argument is there are edge cases we can't cover, well it's matured now. If it's accessibility, we have more accessibility issues with Custom Elements because the lack of customized elements.


It might sound unfair to just pick random quotes, but we haven't been really given a clear reason. The first comment of this thread mentions the Webkit Bug which links to @trusktr 's issue where @rniwa states:

The problem here is that UA internally checks local name e.g. element.localName == aTag to do various things, and all of that code needs to be updated to account for the fact there could be an intense of HTMLAnchorElement whose local name is not a.

Now I'm going to re-iterate that Apple objects to extending subclasses of HTMLElement using is= as currently spec'ed for various reasons we've stated in the past and this feature won't be supported in WebKit.

Originally posted by @rniwa in https://github.com/WICG/webcomponents/issues/509#issuecomment-222860736

To me that reads as though the reason is that it's a lot of work for the Safari team to implement all that code, so Safari would much rather punt all that extra work onto devs.

Edit: More credence to this based on

Subclassing existing elements is hard as implementation-wise identity is both object-based and name / namespace based. Therefore subclassing an existing element (currently) requires that the name / namespace does not change. See also DOM: element constructors.

https://www.w3.org/Bugs/Public/show_bug.cgi?id=28547

That actually be the real reason: the Safari team is just too small and lack resources to make it happen.


Am I wrong? Am I mischaracterizing things? Maybe. But the top comment states:

Colleagues and I wanted to share our thinking around that in this issue and also open it up for feedback from the community.

Well, here it can be clarified and I don't have to search old internet archives to find an answer. I'll end with this quote:

# [03:01] \ sicking: i think the problem, as you correctly pointed out, is that other vendors haven't had enough time to review the specifications :( # [03:01] \ sicking: i sympathize with google in that they've been working on this stuff for years # [03:02] \ but it'll be really unfortunate if we end up not being able to implement what's been spec'ed at the end of the day

WebReflection commented 1 year ago

I am not sure I understand the namespace issue (which other browsers either have as well or solved somehow) but I wonder if an OpenCollective effort from developers interested in having feature parity would be good to sponsor Igalia to help Safari team tackling this ... if it's "too much effort" the real argument still after 10 years and Apple at its top (money-wise).

EisenbergEffect commented 1 year ago

Apologies for the wall of text. A few thoughts...

I know many folks on this thread are very passionate about customizable built-ins and they've been waiting and hoping for full cross-browser parity for a long time. There are an important set of scenarios around this that aren't well-addressed today and most of us would very much like to see that improved...and sometimes we feel a little desperate about it.

I also know that at one time WebKit was opposed to customizable builts-ins for a variety of reasons when it was being spec'd. I don't have the references, but I've read additional threads beyond the above that laid out more of the reasoning, beyond simply resourcing (though resources and complexity are always a factor with any spec). Despite WebKit's concerns not being addressed by improvements to the spec or alternatives at the time, the spec was pushed through and written into the standard anyway. This is not normally how the standardization process works. So, we should all also acknowledge that as frustrated as we may be about this, WebKit has a significant reason to be upset as well.

I'm mentioning this because I think we should all be careful that we don't heap blame on WebKit or badger them or their engineering team. How we got here isn't solely the fault of any one party. Nevertheless, here we are. I think we all agree that we'd like to improve things.

For WebKit

As I understand it, folks who would like to see customizable built-ins land in WebKit would like an updated statement from WebKit as to what their reasons are for not implementing. Significant changes have been made since the original discussions. So, the group would like to understand things like:

@rniwa @annevk Could either of you or someone from your teams help in answering these questions? I don't want to badger you all. I know you've probably got a lot going on. If you need some time, just let us know. Or if you think setting up an f2f to discuss is better, I think most folks are open to whatever we can do to move ahead.

For the Community

I think that if WebKit can come back to us with concrete answers to some of these questions, we owe them to take it very seriously. There are many factors at play in any engineering process, and those are even more complicated in standardization scenarios. Here are a few things we should be willing to do if we are serious:

rniwa commented 1 year ago
  • Is it a philosophical issue that WebKit has and does it still hold that position?

Fundamentally, we don't think there are compelling use cases to justify the existence of this feature, and we don't think this feature adequately solves problems it claims to solve.

Enhancing existing form controls is often brought up as a primary reason for wanting to use customized built-ins but input element and other form controls wouldn't allow attachment of a shadow root, and therefore their appearances can't be easily customized. We think efforts such as open UI and form associated custom elements are better solutions for this use case.

Inheriting ARIA roles and other accessibility feature is also often cited as reason for wanting to use customized built-ins. Yet, browser can't realistically make customized built-ins any more accessible than autonomous custom elements can be since the browser can't assume semantics of an element beyond what the underlying element already implements, which is also possible to specify via ARIA attributes. Default ARIA roles for custom elements and cross-root ARIA are better solutions for these use cases since they can be generalized to support any kind of custom elements, not just ones that implement the same semantics as builtin elements.

For much the same reason we think the progressive enhancement use case falls short of meeting its goal. Using child elements is the established way in HTML to provide fallback contents. E.g., this is how canvas and picture approach it.

While the ergonomics of enhancing the behaviors of elements with special HTML parsing rules may not be great, we like solutions like element behaviors and custom attributes better than customized builtins for those use cases.

  • Is it an issue of underspecified behavior? Do the community or other vendors need to help WebKit figure out a few more details?

The fact each subclass of HTMLElement doesn't provide mechanisms to customize their behavior is problematic but it's not the primary reason we don't support this feature.

  • Is it an issue of overwhelming complexity, given specific and unique details of the WebKit codebase? Does this make the entire feature itself non-feasaible?

No, the issue isn't about overwhelming complexity or anything specific to WebKit's codebase.

  • Is it an issue of resources? If so, would WebKit be open to contributions from experienced individuals in this area?

No, this is not an issue of resource allocation.

WebReflection commented 1 year ago

@rniwa

the browser can't assume semantics of an element beyond what the underlying element already implements

and I think nobody expects that, but here there's a list of elements that cannot be extended for standards reasons:

https://gist.github.com/WebReflection/266897efa84a82534f7ffe17aadaba3b

ARIA attributes there can't help most cases and the only possible progressive enhancement is via builtin extend.

pookage commented 1 year ago

Well, there's our response folks - webkit didn't get their way in how the Standard was created and so are now throwing the toys out of the pram rather than implement it 🙃

inopinatus commented 1 year ago

Dans ses écrits, un sage Italien Dit que le mieux est l'ennemi du bien.

-- Voltaire, 1772.

clshortfuse commented 1 year ago

Thanks @rniwa! I appreciate the feedback and please don't take anything I've said with ill-intent.

I have mostly concerns and this point more than obstacles I feel I can't be worked around. I would like Web Components to be something we could pass easily around between pages. For example, if I like PersonX's implementation of a Button or PersonY's implementation of a Switch to be easily popped into my page. Knowing most of the code is styling and mostly all complexity is handled by the browser (extension of native) means I don't have to worry about compatibility. I can just land in the page in a microfrontend/plugin fashion. And that would be great because there are those who have a great design skill but may not have the best coding practice.

The sad state is we all (Spectrum, Ionic, Material Components, Fast, LWC, and private/solo devs) have our own slew of mixins and behind-the-scenes code to handle ARIA reflection, form submission, focus delegation, interaction states, link handling, etc for their "simplest" of components. It locks out having a wide selection of components for the reasons that you have two choices:

The dream of anyone can build a Web Component dies a bit here. I can't say "anyone" can build a button because I'll have to inspect it and ask:

If it doesn't do this, then it's a custom, out-of-spec button. You have to know and acknowledge (in its documentation) what it can and cannot do. If it does do everything, I would say, show me the tests that prove it does which is still a chore on the devs to verify this. Half of these are part of WPT tests, but not all. Something of these exist because of WCAG AA compliance. Some of these are impossible to satisfy without custom workarounds because they are related to browser bugs. Some of these aren't even part of spec, and just unofficial browser implementations that users/authors may expect. Also people also use style links as buttons, so if you choose to add that proper support for that, it has its own suite of requirements. There's there's other complexities for a "simple" checkbox and text entry (input) element.

This has two consequences. One is that for people who do code all these aspects, the code buy-in is so large, you no longer shop components. You shop framework/component libraries. That's because they bundle and reuse all that code both to simplify handling the complexity as well as reduce the bundle size. It just makes sense to tap into that when also want an Icon Button, Segmented Button, Dropdown Button etc which will likely reuse the mixins that made it possible.

The other consequence is that we lock out people who just want to make a fancy button. It's disingenuous to say it's all so simple. There's way too much that people need to code. Sure the path exists. All the ways to make a custom button just as good as native are there (or, rather will be there after other APIs land). I look at so many Web Component tutorials and guides and just think to myself, "Yeah, that's not enough."


Attaching behaviors to native elements via attributes is very neat and I will probably use them at some point, but I don't see relation to Web Components honestly. I would like changes to be contained to the element itself and whatever is going on underneath. There's a reason .checked, .indeterminate, and .value don't change on the DOM. That's only possible with Shadow DOM. I don't see how you would style internally without a one. We're back to classname hacks and global CSS if you do try. I've done my attaching to element concepts before and the consequence of manipulating light DOM is that either add more light DOM elements, break things like :first-child styling that devs have to learn and workaround, or, you reserve for yourself (and limit yourself) to ::before and ::after. Some of this is solved by committing yourself to a render framework, which tries to work around this, but that another buy-in. Shadow DOM breaks free of this. A <x-button> should be able to hot swap <button>.


At this point, I've done my work, and I'm going to keep at it. Maybe it's sunken-cost fallacy, or maybe the ends with justify the means. I do like having native-like custom component into a one-liner in HTML with just one import swap to make it happen (ie: replace what was a CSS stylesheet import with a JS script import). So when ARIA Crossroot/AOM or whatever comes along, I will code that too and add it into my components. Maybe you feel all the work that comes with making a native-like Web Component is the barrier of entry and it's justified. I don't think it is justified. Again, none of this impossible, but at the same time the bar should not be "as long as it's possible". (That not intended to be a strawman argument)

Of course, I could be wrong. Maybe I'm just bad at all this and needlessly complaining. But my advice, as is stands, is for anyone who wants to script their own "fancy-button" with Web Components: Please don't. You're setting yourself up for a mountain of work to get it right, or risk polluting the landscape with half-functional elements that is poor experience for devs and users alike.

EisenbergEffect commented 1 year ago

@clshortfuse Given button as an example, how does customizable built-ins enable this scenario as the feature is defined today? This is an honest question because after working with Web Components for a while, I haven't seen how this can work. If I've understood correctly, it's not possible to attach a shadow root or shadow-scoped styles. It would seem that to get what you want, not only would you need customizable built-ins as previously spec'd but also additional features added to customizable built-ins in order to handle basic cases like styling. Am I wrong about this? One of the reasons I have avoided customizable built-ins myself is not just because WebKit never agreed to the spec to begin with, but also because the feature just doesn't seem to support the core use cases of design system style customizations of interactive elements. That seems like such a critical scenario that whatever solution we come up with should support that.

clshortfuse commented 1 year ago

@EisenbergEffect Yes, you are right, but to clarify, shadowRoot is limited (currently) to:

You can add styles (shadow root) and the reasons why are probably clear (they're simple). But I honestly write more wrangling what a button does than the actual styling of it. Because I can use shadowRoot, I do and a wrap an HTMLInputElement to save some work and it's still a lot. (Edit: I can also benefit from styled <h1> but that's more SEO related which is low-priority for me)

I've messed around with host only styles because the shadowRoot does have its share of inconveniences and you can do this.animate(styles, {...timing, fill: 'forwards'}) on :host without having to resort to stylesheets. Hacky? Yes. But does it work? Also, yes (though CSS Variables recently fixed on Safari, but not yet on Firefox). You don't have to worry about stylesheets and whether your shadow-deep element will get a global CSS style.

There's a discussion about "Theming/open styling" here but, really, you can inject an adopted stylesheets into the the element's root when it moves (connected/disconnected) that are styled against button[is="fancy-button"], though I forget if connectedCallback is called if an element is moved across shadowRoot which could be an edge case. With WeakRef, there's less risk of memory leakage as well. But even without the shadowRoot, the attribute callbacks and connected, disconnected, and constructor phases are extremely useful and already in use.


Of course, that also brings up the concept of, if Customized built-in elements aren't interesting enough, and worth implementing, maybe we should make them even more interesting like allowing shadowRoot on things like HTMLButtonElement. Would it be opening a can of worms? Possibly, but in Chrome and Safari there are user-agent shadow-roots that exists, even on the <input> element that's void (not sure what Firefox does). But that would be more interesting and perhaps even more justified.

I did reason: "Why push for more when we can't get the basics?", but perhaps the Webkit team would be interested in more complex Customized built-in elements (V2)?

EisenbergEffect commented 1 year ago

I think we should try to focus on the scenarios independent of the customizable built-ins feature. It seems that most folks want to have a built-in element in the DOM, and avoiding replacing it, wrapping it, proxying it, etc. while also adding styles and behaviors to it.

Inheriting from built-ins isn't the only way of accomplishing this. It looks like WebKit is open to element behaviors and custom attributes, which would also enable this scenario. This would switch the model from an inheritance-based approach to a composition-based approach, which is a good change in my opinion. It also aligns more with modern game/rendering engines that tend to be based on ECS models. For example, if you look at something like Unity, you don't ever inherit from their base scene graph node. Instead, you create behaviors that you attach to their built-in nodes, and through that mechanism the entire scene is built. I think we have an opportunity to do something like that as a stronger alternative to inheriting from built-in elements.

clshortfuse commented 1 year ago

If one of the behaviors is "does what a native button does" then I think that's fine as well. That was my initial (erroneous) interpretation of what element-behaviors does. If user agents can give us native behaviors, I'm all for it. We don't have mess to tag-names either. Something in ElementInternals is fine with me and how formAssociated = true works fine. To me that reads as "this custom element has the same behavior as a native form control".

I have mixins that just serve to wrap HTMLDialogElement, HTMLAnchorElement, HTMLTextAreaElement, HTMLInputElement so a behavior for each of them is ideal for me. I like my custom buttons to become HTMLAnchorElement-like when [href] is added and submit-like when [type="submit"] and the ability to during runtime add and drop behaviors would be great.

colepeters commented 1 year ago

Thanks for the followup, @rniwa. Admittedly I was hoping for something different, but I appreciate you taking the time.

Because you mentioned 'using child elements' again, can I ask how you'd go about using child elements when you also want to customize those child elements, but the HTML elements you're working with are incompatible with custom child elements, as in this example I noted previously?

Without custom built-ins, certain patterns like a picture with customized source elements are, at least today, impossible to build. How would you address this, either with existing features or the proposals you mentioned?

rniwa commented 1 year ago

Because you mentioned 'using child elements' again, can I ask how you'd go about using child elements when you also want to customize those child elements, but the HTML elements you're working with are incompatible with custom child elements, as in https://github.com/WebKit/standards-positions/issues/97#issuecomment-1492337466?

Given source element already supports media and width attributes, why would we need my-source in that example? What are concrete ways you're trying to extend source element?

rniwa commented 1 year ago

@clshortfuse in your example, what kind of customizations are you applying given button doesn't allow shadow DOM? I agree that extending buttons is really hard today, and we should fix that as a platform but initiatives such as Open UI seem better suited to solve these use cases.

colepeters commented 1 year ago

Given source element already supports media and width attributes, why would we need my-source in example? What are concrete ways you're trying to extend source element?

In that example, the my-source elements are taking the src attribute passed to their my-picture parent in combination with their own width attributes to create a URL for an optimized version of that image. For example:

<my-picture src='some/path/to/foo.jpg'>
  <my-source width='600'>
  <my-source width='400'>
  <my-source width='200'>
</my-picture>

…would produce the equivalent of:

<picture>
  <source src='/my/image/transform/endpoint/foo.jpg?width=600'>
  <source src='/my/image/transform/endpoint/foo.jpg?width=400'>
  <source src='/my/image/transform/endpoint/foo.jpg?width=200'>
</picture>

We were doing this as a way to allow consumers to pass a single src to my-picture, and then declare dynamically optimized variants of that source image based on the widths of the my-source elements, using an image optimization API. The end goal was to make it easier to create responsive images from a single source image with a range of optimizations.

This pattern can't work if customized builtins aren't supported. (We've since pursued a different option for this component, but there are many other HTML elements where a strict child hierarchy is required, and using custom elements for those children can't work without customized builtins, at least at this point in time. Consider tables, for example.)

rniwa commented 1 year ago

@clshortfuse I see. In that particular example, it seems like using a regular source element would be fine given src-less source wouldn't fetch anything? By the way, you'd need img element inside picture to make that work like this:

<my-picture>
  <picture>
    <img src='some/path/to/foo.jpg'>
    <source width='600'>
    <source width='400'>
    <source width='200'>
  </picture>
</my-picture>

Extending builtin elements like that is actually quite tricky even with customized builtins. Suppose had a markup like this:

<picture is='my-picture'>
  <img src='some/path/to/foo.jpg'>
  <source is='my-source' width='600'>
  <source is='my-source' width='400'>
  <source is='my-source' width='200'>
</picture>

It seems as if this will work perfectly fine as progressive enhancements but picture element will happily fetch some/path/to/foo.jpg without consideration to other variants of the image if my-picture and my-source are defined asynchronously / upgraded as opposed to synchronously defined.

Not putting src on img element will solve that problem but then it's no longer a strict progressive enhancement.

colepeters commented 1 year ago

@rniwa Yeah, I left out the fallback img in that example for brevity, but you're right. my-picture also renders an img with the src attribute as a fallback.

We (Enhance) expand our custom elements on the server, so the markup would be sent to the browser as:

<picture is='my-picture'>
  <source is='my-source' width='600' src='/transform/foo.jpg?width=600'>
  <source is='my-source' width='400' src='/transform/foo.jpg?width=400'>
  <source is='my-source' width='200' src='/transform/foo.jpg?width=200'>
  <img src='some/path/to/foo.jpg'>
</picture>

I had this working as expected in Chrome and Firefox, but because Safari doesn't support custom built ins, we had to scrap this approach entirely, as we didn't want to get into shipping polyfills, etc.

I guess my main point though is that again, many HTML elements are strict about the hierarchy of their children (tables, picture, video, etc). So beyond this specific use case above, what path forward do you see for these scenarios where we'd want to use custom elements as children?

To give another example, say I want to provide the user with custom elements for table, thead, tbody, tr, and td to compose independently?

rniwa commented 1 year ago

Of course, that also brings up the concept of, if Customized built-in elements aren't interesting enough, and worth implementing, maybe we should make them even more interesting like allowing shadowRoot on things like HTMLButtonElement. Would it be opening a can of worms? Possibly, but in Chrome and Safari there are user-agent shadow-roots that exists, even on the <input> element that's void (not sure what Firefox does). But that would be more interesting and perhaps even more justified.

The tricky thing there is coming up with a model of attaching shadow roots more than once that doesn't involve the complexity of multiple generations of shadow DOM.

clshortfuse commented 1 year ago

@rniwa I don't use currently use custom built-in because I'm supporting Safari. I know it's not a silver bullet, but I would be willing to scrap a bunch of code to extend a native one. I won't have to worry so much about knowing if I whacked every mole (bug or use case). Specifically to button, the only things I need shadow root for is layers: hover, pressed (ripples), and outlines. I would be willing to switch to light DOM since the resulting code is smaller and safer.

Google's sample at https://web.dev/custom-elements-v1/#extending-native-html-elements is pretty similar to what I would do implementation wise, including adding a ripple example. There's a point there about not needing to rewrite all its functionality.

I can suffer a DOM tree reflow on connect since it wouldn't be all that different from working with light DOM.

It's not my ideal solution to be honest. At best I'm getting some code consistency with attributeChangedCallback, lifecycle, and registration so customized built-ins would weave neatly with my fully custom ones

On the submit button discussion thread we're talking about adding a isSubmitButton = true to ElementInternals and I think that's the right line of thinking. We are also talking adding the "perform implicit submit" behavior. But considering we already have form associated behavior and are adding more behaviors, it would be nice to also get the full button behavior. Is it really necessary to piecemeal everything related to button?

We probably agree on more than it sounds and I'll take a look where OpenUI can help push something more there. But I will always cede a meh DX experience to an improved UX experience with accessibility being of highest priority. That's why I'd be willing to use the customized built-ins, as inelegant as their are. I would then move over to whatever, more powerful (native behaviors or shadow rooted built-ins) come later, if the code cost were worth it.

rniwa commented 1 year ago

We (Enhance) expand our custom elements on the server, so the markup would be sent to the browser as:

<picture is='my-picture'>
  <source is='my-source' width='600' src='/transform/foo.jpg?width=600'>
  <source is='my-source' width='400' src='/transform/foo.jpg?width=400'>
  <source is='my-source' width='200' src='/transform/foo.jpg?width=200'>
  <img src='some/path/to/foo.jpg'>
</picture>

If you're applying that sort of transformations in the server side, why would source needs to be my-source? Are you adding extra APIs on HTMLSourceElement?

I guess my main point though is that again, many HTML elements are strict about the hierarchy of their children (tables, picture, video, etc). So beyond this specific use case above, what path forward do you see for these scenarios where we'd want to use custom elements as children?

To give another example, say I want to provide the user with custom elements for table, thead, tbody, tr, and td to compose independently?

For those use cases that require special HTML parsing rules, we think proposals like element behaviors element behaviors and custom attributes are better in that they can attach & combine new "behaviors" to any element. Note that these elements don't allow attachment of shadow roots either so the amount of customization allowed is limited anyhow.

rniwa commented 1 year ago

On the submit button discussion thread we're talking about adding a isSubmitButton = true to ElementInternals and I think that's the right line of thinking. We are also talking adding the "perform implicit submit" behavior. But considering we already have form associated behavior and are adding more behaviors, it would be nice to also get the full button behavior. Is it really necessary to piecemeal everything related to button?

We probably want to break up a native button into coherent pieces that could be stitched up together to build a button easily. I agree ergonomics isn't great right now.

colepeters commented 1 year ago

@rniwa

If you're applying that sort of transformations in the server side, why would source needs to be my-source?

Sorry, I'm a bit confused by the question. Just because we're rendering on the server doesn't mean we don't want to use the platform. It's a goal of Enhance to leverage the platform as it is, and make some of the hard parts easier — while still producing semantically correct code that adheres to web standards and agreed upon specs.

The alternative I presume you're suggesting (please correct me if I'm wrong) is allowing the user to write something like:

<picture src='path/to/foo.jpg'>
  <source width='600' media='…' />
  <source width='400' media='…' />
</picture>

…and then return some markup from the server like:

<picture>
  <source width='600' media='…' src='path/to/transform/foo.jpg?width=600' />
  <source width='400' media='…' src='path/to/transform/foo.jpg?width=400' />
  <img src='path/to/foo.jpg' />
</picture>

But why would we do that when the web components spec gives us a way to do this programatically, via extending built in elements with web components? Hence, we would provide our users with my-source, which would extend the source element.

For those use cases that require special HTML parsing rules, we think proposals like element behaviors https://github.com/WICG/webcomponents/issues/727 and https://github.com/WICG/webcomponents/issues/1000 are better in that they can attach & combine new "behaviors" to any element.

That issue for Element Behaviours was closed by @annevk and hasn't had any activity since March of 2019, and the custom attributes issue seems to have no discussion at all yet. If Webkit believes these are the way forward, then that's great, but then it has to be on Webkit to actually push on these, since customized builtins are available in every other modern client. The seeming lack of action is really frustrating for those of us with real uses cases that we can execute everywhere except in Webkit clients. (I'm not putting that frustration on you, just to be clear.) How can Webkit help those of us with valid use cases to move forward here?

rniwa commented 1 year ago

@rniwa

If you're applying that sort of transformations in the server side, why would source needs to be my-source?

Sorry, I'm a bit confused by the question. Just because we're rendering on the server doesn't mean we don't want to use the platform. It's a goal of Enhance to leverage the platform as it is, and make some of the hard parts easier — while still producing semantically correct code that adheres to web standards and agreed upon specs.

The alternative I presume you're suggesting (please correct me if I'm wrong) is allowing the user to write something like:

<picture src='path/to/foo.jpg'>
  <source width='600' media='…' />
  <source width='400' media='…' />
</picture>

…and then return some markup from the server like:

<picture>
  <source width='600' media='…' src='path/to/transform/foo.jpg?width=600' />
  <source width='400' media='…' src='path/to/transform/foo.jpg?width=400' />
  <img src='path/to/foo.jpg' />
</picture>

But why would we do that when the web components spec gives us a way to do this programatically, via extending built in elements with web components? Hence, we would provide our users with my-source, which would extend the source element.

But then the progressive enhancement doesn't work because picture / source without a functional src doesn't work? If we're specifying src on source, then we'd end up doing an unnecessary fetch so it doesn't seem to work either way. Maybe I'm misunderstanding exactly what you're trying to achieve.

For those use cases that require special HTML parsing rules, we think proposals like element behaviors WICG/webcomponents#727 and WICG/webcomponents#1000 are better in that they can attach & combine new "behaviors" to any element.

That issue for Element Behaviours was closed by @annevk and hasn't had any activity since March of 2019, and the custom attributes issue seems to have no discussion at all yet. If Webkit believes these are the way forward, then that's great, but then it has to be on Webkit to actually push on these, since custom attributes are available in every other modern client. The seeming lack of action is really frustrating for those of us with real uses cases that we can execute everywhere except in Webkit clients. (I'm not putting that frustration on you, just to be clear.) How can Webkit help those of us with valid use cases to move forward here?

We'd be interested in pursuing solutions in this area just as much as we're interested in solving use cases addressed by template instantiation / DOM parts API (we just had F2F this morning to that end). While we're more focused on addressing urgent needs like making ARIA work with shadow DOM for now but we're more than happy to engage in that discussion (perhaps not in this standards issue though).

colepeters commented 1 year ago

But then the progressive enhancement doesn't work because picture / source without a functional src doesn't work? If we're specifying src on source, then we'd end up doing an unnecessary fetch so it doesn't seem to work either way. Maybe I'm misunderstanding exactly what you're trying to achieve.

We may both be misunderstanding each other here, and I don't want to derail this issue too much more for the sake of this one use case, so I'll put a pin in this part of the discussion ;)

We'd be interested in pursuing solutions in this area just as much as we're interested in solving use cases addressed by template instantiation / DOM parts API (we just had F2F this morning to that end). While we're more focused on addressing urgent needs like making ARIA work with shadow DOM for now but we're more than happy to engage in that discussion (perhaps not in this standards issue though).

Where would it be best to discuss the need to address custom elements playing nicely with parent elements that have strict child hierarchies (picture, table, etc.)?

chrisdholt commented 1 year ago

@clshortfuse Given button as an example, how does customizable built-ins enable this scenario as the feature is defined today? This is an honest question because after working with Web Components for a while, I haven't seen how this can work. If I've understood correctly, it's not possible to attach a shadow root or shadow-scoped styles. It would seem that to get what you want, not only would you need customizable built-ins as previously spec'd but also additional features added to customizable built-ins in order to handle basic cases like styling. Am I wrong about this? One of the reasons I have avoided customizable built-ins myself is not just because WebKit never agreed to the spec to begin with, but also because the feature just doesn't seem to support the core use cases of design system style customizations of interactive elements. That seems like such a critical scenario that whatever solution we come up with should support that.

Forgive me @EisenbergEffect for jumping in here without fully reading everything since your comment - but I think there's value even if shadow roots aren't supported for certain primitives. If we look at a singular implementation where any primitive must have scoped styling, then yes - there's absolutely missing features here. However, I'm not sure that's universal and I don't know that it dismisses the reality of what is possible. Now, if I think of a scenario where I want to inject just a web component button into an existing React codebase...in that case the styles will be applied to the global document without encapsulation and any value is lost - 100% agreement. However, if I'm building more composed custom elements, built-ins can have styles scoped to the corresponding is attribute as noted above and their styles can be attached to the nearest ancestor shadow root which provides a level of encapsulation without the cost of attaching a shadow root to the primitive. IMO, this is a nuanced cost and trade-off, but there is a case for built-ins without a shadow root that makes sense to me here. I don't think a shadow root should necessarily be required for all design system scenarios - I think it's how we've commonly looked at and approached the problem, but that's also brought with it certain issues as you've noted pointing to your proposal.

More broadly on the feedback here from webkit on a11y...Without cross root ARIA landing in whole, I struggle to see a valid argument for autonomous custom elements being as accessible as customized-built-ins across all scenarios. An HTML button is a button. It has proper semantics and interactions unless a dev does something "bad". This is not new and can manifest in the platform today in this scenario and almost certainly if the expectation is to build <fancy-button> with ARIA. Even an autonomous custom element which serves a shadowed button has a host of issues that need to be mitigated, not the least of which is passing ARIA from the generic host to the internal element which requires proxying the button API and hacking around certain AOM issues. Cross Root ARIA will solve this eventually, but that work is in process and not implemented anywhere - here, Safari is the outlier and I have yet to see a good defense of the a11y argument.

I can appreciate "I prefer X instead of Y", but I don't think that there have been compelling reasons offered here for not implementing the spec and that is disappointing.

Edit...adding an additional question :) - @annevk can you elaborate on this?

The syntax used prioritizes implementers over web developers which is wrong per the Priority of Constituencies.

I'd love to better understand how Apple sees this as violating the priority of constituencies - is there a more robust answer you could share so I can better understand?