A (probably not) unbiased critique of Web Components, among other things

The Problem with Frameworks: Longevity and the Rise of Complexity

Keith Cirkel begins by highlighting React's popularity and dominance in the front-end development landscape. While acknowledging React's good developer experience (DX), he points out the increasing complexity of the framework, citing examples like useState, useEffect, useLayoutEffect, useMemo, and React Forget (now React Compiler). Cirkel argues that this complexity, combined with the fact that most popular front-end frameworks now ship compilers, raises concerns about the long-term viability and portability of projects built with these frameworks.

The Quest for Longevity: Moving Beyond Framework Dependence

Cirkel shifts the focus from adoption rates and performance to longevity, using the example of jQuery UI to illustrate how dependence on a specific framework can lead to the demise of otherwise valuable projects. He argues for building applications in a way that minimizes dependence on any one framework, emphasizing the importance of portability and interoperability for long-term sustainability.

Embracing the Platform: Shifting the Sedimentary Layers of Applications

Cirkel proposes a shift in thinking about web development, advocating for leveraging the power and stability of the platform (HTML, CSS, JavaScript) instead of relying heavily on frameworks. He suggests reducing the framework's footprint within applications, allowing for greater flexibility and portability in the long run.

Web Components: A Viable Alternative for a Sustainable Architecture

Cirkel introduces Web Components as a potential solution, providing a brief overview of their capabilities and advantages. He highlights their cross-browser compatibility, framework interoperability, and the potential for building reusable and encapsulated UI elements. Cirkel acknowledges that Web Components are not perfect but emphasizes their value as a tool for decoupling applications from specific frameworks.

Web Components Crash Course: Basic Concepts and Lifecycle Hooks

Cirkel dives into a technical overview of Web Components, explaining the fundamental concepts, including defining custom elements, lifecycle hooks (connectedCallback, disconnectedCallback), and integration with frameworks like React. He demonstrates how to create simple Web Components, define properties, methods, and handle events.

Understanding Shadow DOM: Encapsulation and Reactivity in Web Components

Cirkel introduces Shadow DOM, a crucial aspect of Web Components, explaining how it enables encapsulation and isolation of styles and behavior within a component. He discusses the benefits of Shadow DOM in terms of preventing style collisions and creating truly modular UI elements. He also addresses common challenges related to Shadow DOM, such as working with slots and ensuring accessibility.

Navigating the Nuances of HTML and DOM: A Common Pitfall in Web Components

Cirkel delves into the subtle differences between HTML (the serialization format) and DOM (the browser's internal representation of a web page). He explains how these differences can lead to issues, especially when working with Shadow DOM and frameworks like React. He highlights the importance of understanding these distinctions to avoid potential pitfalls and ensure proper rendering and hydration of Web Components within different environments.

Addressing DRY Concerns and Slot Limitations in Shadow DOM

Cirkel acknowledges concerns about code repetition when defining Shadow DOM templates for Web Components. He suggests potential solutions like using server-side templating languages or exploring upcoming features such as named templates and declarative custom elements. Cirkel also addresses the limitations of slots (placeholders for external content within Shadow DOM), explaining how they cannot be nested and how this can impact component composition. He suggests workarounds and highlights ongoing discussions within the Web Components community to improve slot behavior.

Ensuring Accessibility: Overcoming Challenges in Shadow DOM

Cirkel tackles the topic of accessibility in Web Components, emphasizing the importance of making components usable for everyone. He explains how Shadow DOM's encapsulation can create challenges for assistive technologies and outlines strategies for ensuring accessibility within components. He discusses issues related to ID referencing and ARIA attributes, proposing solutions like the ReferenceTarget proposal and ARIA mixins to improve accessibility support within Web Components.

Styling Web Components: Encapsulation, Inheritance, and Customizing Styles

Cirkel delves into styling Web Components, explaining how Shadow DOM affects CSS inheritance and how to apply styles within the encapsulated scope. He discusses the challenges of using global stylesheets with Shadow DOM and suggests alternative approaches like CSS variables, CSS Modules, and scoped styles. He also explores techniques for creating customizable styling APIs using CSS custom properties and the ::part pseudo-element, enabling developers to create flexible and themeable Web Components.

Embracing the Future of Web Components: A Call to Action

Cirkel concludes by reiterating his belief in the potential of Web Components, encouraging developers to explore and adopt them as a path towards building more sustainable and future-proof web applications. He acknowledges the challenges and learning curves associated with Web Components but emphasizes that the benefits outweigh the costs in the long run. He encourages developers to engage with the Web Components community to contribute to their evolution and overcome existing limitations.

```

There's a QR code you can follow along.

I'm also going to have QR codes for different parts of this because this is going to be a bit of a deep dive.

But yes, we have a problem.

React is incredibly predominant, right?

It's been very highly rated in terms of usage on the State of JS survey for the last seven years running.

That's fine because React has good DX, right?

In React, UI is this function of state.

Except useState, which is state inside the function, and useEffect, which actually you probably shouldn't use, because it gets called a lot.

So you could use useLayoutEffect, but that won't run on your server, only useClient.

Maybe you can just use.

Don't forget you probably want to use memo.

Actually, do forget that, because React Forget will now use Memo4U, but that's called React Compiler now.

Sorry, cheap, digs.

But, speaking of compilers though, the majority of the popular front end frameworks now ship compilers.

And even doing so, they're less efficient than well written JS.

And so we're opting into all this complexity, and I can't really see why.

And Preact and Lit, they carry their own weight here.

In this performance benchmark, they're doing okay, and they don't ship compilers, right?

So why are we opting into all this complexity?

And actually, if we go back to the state of JS survey, I think we can see that satisfaction in React is dipping, interest and retention rates are lower year on year, and I think this is in response to that complexity.

I think it maybe shows that we are on the tail end of React adoption, and no talk on React is complete without jQuery.

I don't really care about adoption figures, and I'm not super concerned about performance or DX, right?

I'm less concerned about those.

My real issues are about longevity.

And jQuery UI was arguably one of the first UI libraries, that or YUI.

It came with all sorts of plugins for drag and drop and accordions, buttons, dialogues.

It's now one of OpenJS's emeritus projects, which means it's effectively deprecated.

They don't recommend you use it.

And I think like many React projects, jQuery UI belonged to its dependencies, it belongs to jQuery, so its lifetime is bound to its parent.

And so I see wonderful projects around, built around React, and I weep because I think that so much development gets lost at the alters of these ecosystems.

But I'm not here to critique React too much, and I'm not really here to talk about frameworks in general.

There's enough on the web to read about these things.

Zach Leatherman has compiled a great guide on the React criticisms.

It might be worth checking that out.

I think there's one universal thing we can all agree on.

Likely in ten years we will be talking about React like we do jQuery today.

I think our business logic doesn't need to depend to its, belong to its dependencies.

I think a big problem with these frameworks is just a lack of portability.

There's a lack of interop.

And so we lock ourselves in and then we get stuck in this, in this sunk cost.

And big organizations might fail this acutely as multiple teams rest on different technology stacks.

Maybe through acquisitions or like autonomous teams, a small organization building a product might feel they've gotten away with it, right?

We build, we're all aligned on a single framework.

And in a snapshot of time, you're right, but time isn't our friend.

Okay, so where do we go from here?

What can we do about this?

If we know these frameworks are finite, do we just give up on them?

Do we just migrate?

It's all essentially moot, right?

Even if we wanted to, we can't just uproot our entire front end code base to use Svelte or Solid or whatever the hot newness is.

Spending hundreds of hours and thousands if not millions of dollars just to get a parity rewrite, it undermines your users.

They want features and they want stability.

They don't care about what framework you're using.

As Joel Spolsky says, a rewrite is the single worst strategic mistake a software company can make.

And if you did rewrite, you're just going to switch to another framework or just another ecosystem.

There are no silver bullets here.

Technology is not a salvation, right?

Nothing is going to solve your problems.

It's fun so far, right?

If we slice across the cross section of our apps, and we look at the kind of sedimentary layers of our applications, such a large portion gets owned by the framework, and this coupling escalates the cost of these tools.

We need to build in a way that kind of doesn't shackle us from one set of dependencies to another, right?

And we want to move from this left where the framework owns a giant portion, more towards the right where we can leverage things like the platform.

This lets us more easily dig our way out of some of these tools if we've made the wrong decision.

The baseline output for all these libraries is HTML and DOM, and it's been around for 30 years and it's probably going to be around for another 30, we've heard about that today.

So if we can get close to these, maybe we have a chance of portability, and surely we can leverage the platform, right?

The platform has an answer here, but, no, HTML Web Components, aka custom elements.

That's what, we're here for, that's the real talk.

Hi, my name's Keith Cirkle, I joined GitHub in 2017, seven years ago, no, yeah, seven years ago now.

GitHub was an early adopter of Web Components, we've been using them since about 2014, and I'd never used them, I was a React developer, and then I came in and, I learned about them, and I became a fan, right?

I'm actually enough of a fan that, I've written a guide on how to use Web Components.

It's free, you can, it's very rough still, a lot of work to do on it.

But yeah, you can visit the QR code.

I'm not trying to sell this.

I'm not trying to sell you on Web Components.

I don't really have any skin in the game.

I do want people to adopt Web Components because I think they're a really good technology that can lead us to a better path in the industry.

As you heard, I'm from the UK.

And I think us Brits have a penchant for pessimism, maybe due to the kind of perpetual rain or rotating door of inept governments, but so while I'm a fan of web components, I'm not afraid to tell you, they're not perfect.

And I'm, I feel duty bound to speak critically of them.

So when I was invited here to talk about Web Components, I thought, what's the talk I want to hear?

I want the gore, right?

And the full half of the glass is that Web Components are great.

They let you shift your sedimentary layer.

You can use more of the platform.

They're fun to author.

And there's new things that get added which make them more fun and more powerful.

But, I'm interested about the, empty, half of the glass.

And so while I'm an advocate, they're not my baby, I didn't invent them, I'm not bankrolled by some kind of VC company.

The talk I want to hear is about the problems that you're going to hit with them, the adoption blockers.

I'm, I think I would learn a lot more from, learning about the problems and the solutions.

So that's what I'm going to do, a technical deep dive.

So brace yourselves, we're going to look at some code.

Okay, so here's, a 30 second crash course into web components just to get everyone up to speed.

You've probably seen this before, but this works everywhere in all of the browsers, even like SeaMonkey and Lady Bird and all of these sort of fringe browsers.

This is the API for web components, right?

So you, define a class and you call customElements.Define, you give it a tag name, it has to have a dash.

And when you put that tag name into the dom, you'll start firing these, connectedCallback, and you can think of this a bit like React classes if you ever used those, if you didn't, this, connectedCallback is maybe a bit like componentDidMount or UseLayoutEffect.

disconnectedCallback is the reverse, it's like componentWillMount.

There's a few other lifecycle hooks, you can go look at those, and, custom elements, they're not just available in every browser, they're available in every framework.

You might have heard about React 19, that was the latest version of React to, pass this custom elements everywhere test suite.

And this is a, test suite that the Web Components Community Group maintains to demonstrate how custom elements can interoperate with all of the frameworks.

And so as a consequence, you can slot these web components directly into your applications today in any framework.

You don't need new libraries or wrappers or anything, you can take small pieces of your app and introduce custom elements into them.

And again, custom elements are classes, and if you know JS, you know how to do components.

If you don't, hopefully nothing is new here in these slides, but, you can do private fields, and you can do, public methods.

And you can do these getters and setters, and you can issue side effects from these getters and setters, and this is the reactivity of JavaScript, right?

It's boilerplate y, it's not the prettiest thing in the world, but there's no magic here, this is just bread and butter JavaScript.

And if you're integrating into React, you'll probably want a useRef, and that way you can call the imperative methods on the class, and also, since React 19 Any prop on the class is going to call the property on the class, right?

Any prop on the JSX is going to call the property on the class.

So here it's going to call this setter function.

And if you issue callbacks, you can use the ref to access the imperative API, like I said.

And because the class always has to extend from HTML element, You get the things that HTML element gives you for free, so one of those is events.

And again, with React here, you can dispatch an event, and you can listen to the events using JSX and using React.

It's worth pointing out here, this new event Tick, is, has got a capital T, they're case sensitive.

Let's talk about web components in terms of HTML and Shadow DOM.

All of that was behavioral stuff, but web components are components, they need their own subtrees, right?

You can use old school DOM manipulation, you can set innerHTML.

It's fine, it's a little, it's not the fastest and it's a little ugly.

But it's whatever.

If you're integrating into React though, you might run into some issues because React likes to reconcile the virtual DOM that it has against the real DOM and sometimes this can cause conflicts.

And introduce the Shadow DOM, which is a paradigm shift from what you might be used to.

There's a bit of a learning curve with Shadow DOM.

It can be quite steep if you're not familiar with these things.

But the Shadow root is like a little, document within your page, like a spicy iframe.

Shadow roots, they're fully encapsulated, so things like IDs and styles don't translate across necessarily, but also unless you have a reference to them, you can't mutate them, and so things like React won't be able to traverse into them, so they're hidden from frameworks like React.

While you don't have to use Shadow DOM You can build web components without, I think Shadow DOM is worth exploring, it's, really powerful, and it's also how browsers work, so you might be familiar with this, but you can turn on user agent shadow routes in the web, DevTools Inspector, and you can see that browsers, build, the internal elements using Shadow DOM, and so an input is really just a handful of divs, and obviously they have the full capability to wire up all of the accessibility correctly, so I wouldn't recommend doing this yourself, but, yeah, like an input is just a handful of divs in the browser.

It's convenient using, the user agent, Shadowroot Inspector, because you can see, things like the pseudo selectors here.

Okay, so we're going to get a bit more technical.

I want to clarify something about DOM and HTML, and so bear with me on this, right?

DOM is what browsers use for UI, okay?

DOM has a standard serialization format that is called HTML, okay?

Alright, bear with me.

These aren't one to one, okay?

And this is where it gets a bit tricky.

Attributes can only be strings in HTML, because HTML is a string format.

HTML also has restrictions on elements.

For example, you can't nest certain elements within another.

A button can't be in a button.

You can in DOM, so you might think, Actually, I've done that before.

You've probably done it in DOM.

HTML has far more restrictions than DOM.

In this example This is how you would represent a shadow root in HTML.

And this is newly available in some of the browsers.

You use this template element, and you pass in the shadow root mode equals open attribute.

And when the parser, the HTML parser, encounters this tag, it will construct a new shadow root for the element.

And so the resulting DOM won't have the template element, it'll have the shadow root.

Okay.

If you look at your DevTools Inspector, You'll see this hashtag shadowroot, and it has this open mode because you set it to be open.

If you see template shadowroot mode equals open in your DOM inspector, something went wrong.

And, to really send this home, this is what DOM APIs look like, right?

And so document.createElement, and, setAttribute, this is how you would do that set of operations imperatively using DOM APIs, where you would get the bottom example here, the something went wrong example.

Remember, this idiosyncrasy, this kind of idiosyncrasy has existed in HTML, time immemorial.

But I think it's, something that people trip over with, shadow DOMs, especially because of frameworks.

Because if you look at a framework like React, it tries to gloss over these differences.

And so this is valid JSX, but it does two different things depending on where you use it.

React DOM Client will turn JSX into DOM operations.

And so this will give you the bad thing in React DOM Client.

React DOM, render to string, or the render to pipe or whatever, is typically done on the server, and so you'll get HTML, and that will kick in the browser's HTML parser, and so this will actually work if it comes from the server.

And so in this way, I think the framework has just simply failed to abstract away these differences.

React tries to suggest that there's this one to one relationship between the DOM and HTML.

And you've probably seen, if you've worked with React before, things like hydration errors.

This sort of thing will cause a hydration error in React.

But this isn't the fault of HTML and DOM, right?

I just think that this is something that React needs to resolve.

And React isn't the only one.

Lots of frameworks have this issue.

The issue, React 26071, goes into some detail around this, if you want to find out more.

One solution for this is, other than React just fixing it, is you can use the userland library.

So I found this react shadowroot library, I just wanted to shout it out, I haven't actually used it, but I looked at the code, it looks reasonable, it looks like it could fix this.

Svelte has similar issues.

I looked at the issue tracker, but I'm just focusing on React, throughout this, presentation.

You'll have to find out if you've got, if you're using a different framework, figure out those issues yourself, I'm afraid.

So yeah, I don't know if the distinction between HTML and DOM has really been internalized by a lot of folks, especially in the framework industry.

I see some of these issues pop up a lot.

Like a very common one is like the, trailing slash at the end of a element is, not actually a thing in HTML that just gets swallowed by the parser and ignored.

Okay, let's talk more about components, though, I think a big grievance with, this kind of style of declarative shadowroot is there's no mechanism to keep this DRY, speaking of, and so you need to repeat this template tag every single time you declare the element.

And this looks really bad, but I think actually when you use these in earnest, when you use, you're working with this in production, the reality is you're probably using a server side, templating language, which will amortize, the cost of this, right?

You're not going to be, typing out this HTML every time.

And plus, the over the wire cost is negligible because of things like gzip, right?

Gzip just, gobbles this up.

This is, what gzip is for.

But it's not ideal, and so the Web Components community group are discussing how we can change this, and so there's, various ideas about, having a named template for an element, or maybe declarative custom elements.

Web Components 1009 is the issue to follow.

Okay, that's not the only other learning curve with a shadow DOM, right?

The other thing is that it essentially swallows any direct children once you have a shadow root.

You can see here, I've got this giant header that's just disappeared.

Or, at least you think it's disappeared.

The thing here is that, shadow roots can have slots.

And this is one of the power features of, shadow roots.

Here you can see what has been rendered is a button when I, what I really want is the giant header.

But, yeah, you can add this slot element and you get both, you can, and so in this way you can interleave what is referred to as the light DOM and the shadow DOM, and so you can combine slots in various ways to have constituent parts of your custom elements shadow DOM as well as, children that get slotted in.

Slots can also have default content, and so whatever you put inside the slot gets displayed if there's no light DOM children.

So you can see this first example, it has no direct children, and so it's just displaying the default content, whereas the second one, it has this default content and, sorry, it has this light DOM node, and so the default content gets hidden, and the, DOM, the text node here gets displayed.

Slots can be named as well, and so you can do this reordering thing, which I think is really interesting.

So here I've swapped the order of these by changing the slot name.

This allows for kind of out of order HTML.

Given HTML is a streaming format, this actually means, that you can do this kind of out of order streaming, right?

And so you can send a chunk of static UI.

Which contains, skeleton UI, like a loading spinner, and then your server can do a bunch of waiting for stuff, and because it's streaming, the browser can start rendering these things, and then it can, put in the, slotted content afterwards.

And I don't know if you saw the little subtle effect there.

Yep, there we go.

And, this is like zero JavaScript, right?

This is just HTML.

I think this is really cool.

For a more capable, practical demonstration of this, this dsd000 that's the link to follow, Okay, so one big gotcha with slots is you can't nest them, you can, only work with direct children, and so if you've got this, div here, this becomes a bit of a problem because the div, it will try and slot the div, but the div doesn't have any named slots, so this doesn't get slotted.

Let's talk about accessibility.

I hope you've been following along.

Accessibility is going to get very complicated now.

We're ramping up.

A big gotcha with slots is that the element itself is hidden from the accessibility tree, right?

So the slot element isn't available in the accessibility tree.

Its children are visible, but you can't have an allowable role on a slot, and that means HTML, like this doesn't work.

And, also the role attribute, it, it works on every, or almost every HTML element except for stuff in meta.

In the head, like meta, title, and script.

But slots also can't have a role.

I think this is just a little bit weird.

There's some issues around tab index on slots as well.

And so AAM456 discusses some of these issues.

You can hit the QR code or type in that link.

So the, fix here is that you need to wrap this in a div.

And, this is not ideal, right?

I can't see a hugely practical reason why slots have this restriction.

And that's what the issue is there.

We're going to try and fix that.

Another big issue is because we have this encapsulation, it can be tricky to get accessibility right.

You might have heard web components aren't accessible.

That's not strictly true.

But I think a big reason why it gets that reputation is that the ID encapsulation becomes a bit of a problem, right?

And so here, for example, we're, saying that this button is described by this tooltip, but the computed description for this tooltip is going to read something like description for button close, because it just reads all of the, text notes and everything, right?

And you can't get to the ID description from outside of the shadow root.

So there's a couple of proposals to fix this that I'm going to talk about.

One is called ReferenceTarget.

And the way ReferenceTarget works is you have this kind of unwieldy attribute on your shadow root called ShadowRootReferenceTargetMap.

And that says, if I'm pointed to as an ARIA described by, forward that to the description ID.

And in that way, it will do the fix up for you.

This, solves the problem quite well.

But as you can see, it's a proposal, it's being prototyped in Chrome at the moment but hasn't really made much progress other than that.

But this could be a real blocker, because this suddenly, the complexity of your accessibility really ramps up in difficulty.

There's another solution that you could look at, which is this imperative assignment of ARIA properties, sometimes referred to as ARIA mixin.

And these properties let you assign various ARIA.

Such as ARIA described by, again, limited availability, a bit of a running theme, for some of these issues.

Safari shipped this, Chrome is definitely shipping this very soon, it's, in the works in Firefox.

You can use some of the ARIA mix in properties, not the element ones, but you can use stuff like role and ARIA description.

And so you can do this computation yourself, right?

You can assign a string and compute all of this yourself, but that gets really tricky quickly.

All right.

Let's do styling now.

Okay.

So styling has the same kind of issues with encapsulation.

I think it's more about a misunderstanding about how CSS works.

But let's give you a quick rundown, okay?

So you can have a style tag inside of a shadow root, and it will apply the CSS that you give it.

And this encapsulation means that you don't actually need to care all that much about how your selectors are formed.

You could just write whatever, because you have this mini page, it's fully encapsulated.

If you want to use an ID selector, you can, even though that might be considered bad practice on a global stylesheet.

Any link or style tag within a shadow root only applies to the shadow DOM, and unfortunately that means, The other way is also true.

Styling and Shadow DOM from the outside, is also encapsulated.

And so again, this is a paradigm shift for teams because you're probably using global CSS libraries, like you might be using Tailwind or a design system stylesheet or something like that, where you need to add your stylesheet to each shadow root and it's really easy to do that in a slide, but try and do this in a very complex system and it gets really awkward to ship to production.

One quick solution is you can slot the element, so if you want a button to be styled globally, put it in the light DOM and slot it, and then global styles will apply, but this doesn't nest very well, and so there's some complexity there.

Some mitigation strategies.

Did you know?

That CSS in JavaScript is a thing.

You can new a CSS style sheet call replace sync and call, adopted style sheets and just shove a style sheet, right?

Here's the new style sheet.

And then you just push the style sheet into the dom.

This works on document and it works on shadow roots.

So yeah, you can do this and, With the new import attributes, you can do this and this will give you a constructed style sheet back, right?

This import gives you that object.

So again, the trickiness here is like, how do you manage these assets in a production system?

Contrived slides is one thing.

So, one really easy way that we could solve this is to just to add this one line of code.

And have this kind of open stylable, shadow root, where it just says inherit all of the document's style sheets, but this doesn't work, and the reason this doesn't work is because a newly constructed style sheet has a special flag on it saying it's a constructed style sheet, and when you push a style sheet into adopted style sheets, if it doesn't have that flag, it throws an error, and there's some technical barriers around.

, CSS working Group 10013 is the issue to try and resolve that.

CSS Working Group have committed that they're going to try and resolve this to make this kind of code possible today.

You can do it today with this, hack, where you, iterate through all of the stylesheets and you reserialize them and you reparse them, but it's a fudge.

There's a userland library called Halflight that you could look at.

So you mark a stylesheet as dash cross route, and then it, does that kind of hack for you on your behalf.

Ultimately though, my preferred solution is to lean into the Shadow DOM, right?

Try and detach from this notion that you have global stylesheets, that like influence the Shadow DOM in that way, and you can do more fine grained control within a Shadow Route.

For example, CSS variables, so CSS variables inherit, and, an unknown property will inherit, and inheritance works within shadow boundaries, right?

So this works today, you can do this, so if you've got a set of design tokens you can just apply those to your shadow DOMs.

But you can also use Shadow Roots to, expose different parts, this is minting your own, pseudo elements, right?

And so you can use ::part in your CSS style sheet and just, have a well known name like button for all of the parts that are buttons and then you can style them accordingly.

Here's some more.

You can see this ::part syntax.

Widely available.

You can also mint your own pseudo selectors with the colon state selector, and so you can start to build up your own CSS API, right?

You can start to think about states and state transitions and parts, and build your own custom elements in that way.

In reality, I think there's like some restrictions around styling, but I think that they're, they can be overcome.

You need to bend it to your will a bit, but you can figure it out.

And I think the result is something more delightful often.

Okay.

There's a ton of smaller issues, but I'm already five minutes over time.

I could probably devote another talk to all of this.

I don't think any of them are critical blockers though, right?

I think this is the worst, the gnarliest stuff.

Like I said, no technology is perfect, right?

No technology is, salvation.

You can memorise, if you can memorise all the ways that UseEffect works, I'm sure you can memorise some of this stuff and I'm sure you can internalise shadow encapsulation.

If it isn't evident already, which I could understand why it wouldn't be, I still think you should adopt Web Components, right?

I'm just not trying to sell you on them.

I want you to understand the problems that you're going to hit with them.

I also want to recognise that there's a bunch of work being done to improve these and I think the Web Components Community Group is a great place where people are coming together to Recognize these problem areas and work towards solutions.

And so I'd encourage you to join.

If you're looking at adopting Web Components, just hit up this link up here.

Join the Web Components community group and start talking about your issues as well.

We welcome everyone.

So I framed at the beginning of my talk how we need to detach from these siloed framework ecosystems.

I think Web Components offer a viable alternative.

And you can see there's some paradigm shifts.

But I also think there's some really cool features, right?

I think they can complement your existing code base.

And I think you can still keep your existing frameworks, like React, and you can still use Web Components to uncouple as much or as little as you want.

But I think it's the sustainable architecture that can outlast some of these frameworks.

Thank you for your time.

Special shout out goes to Mu-An, who recommended me for this talk.

Any questions, if we have time?

Session Details

A (PROBABLY NOT) UNBIASED CRITIQUE OF WEB COMPONENTS, AMONG OTHER THINGS

Keith Cirkel

Software Cyber Shepherd
GitHub

A chart showing the adoption rates of various web development frameworks from 2016 to 2022. Frameworks included are React, Angular, Ember, Vue.js, Preact, Svelte, Lit, Alpine.js, Solid, Stencil, and Qwik.

UI = f(state)

  • useState
  • useEffect
  • useLayoutEffect
  • "use client" or "use server"
  • useMemo
  • react compiler
a performance comparison table of various JavaScript frameworks across three metrics: execution duration, memory allocation, and transferred data size, each measured in geometric means with a 95% confidence interval. Frameworks like 'vanillajs-wc', 'svelte', 'react', and others are evaluated. Color coding from green to red illustrates performance from optimal to suboptimal across the frameworks.
A line graph illustrating the usage trends of various JavaScript frameworks from 2016 to 2022. The frameworks include Svelte, Qwik, Solid, Vue.js, React, Lit, Preact, Stencil, Alpine.js, Angular, and Ember,.

jQuery user interface

But this isn't a talk about React

Decouple the framework

Technology is not salvation

Two stacked diagrams side by side. Both diagrams have three layers: the top layer labeled "Application" in blue, the middle layer labeled "Framework" in red, and the bottom layer labeled "Platform" in green. On the left the framework is larger than the other two layers on the right smaller and the platform in particular is larger,

Blockchain

HTML Web Components

(aka Custom Elements)

A (probably not) unbiased critique of Web Components, among other things

class MyCustomComponent extends HTMLElement {
	connectedCallback() {}
	disconnectedCallback() {}
	}
	customElements.define(
		'my-custom-component',
		MyCustomComponent
	)
	

custom-elements-everywhere

Illustration of a laptop with a smiling face.
class MyCounter extends HTMLElement {
	#count = 0
	get count() { return this.#count }
	set count(value) {
		this.#count = value
		console.log('side effects!')
	}
		add() { this.count += 1 }
	}
	

class MyCounter extends HTMLElement {
    #count = 0
    get count() { return this.#count }
    set count(value) {
        this.#count = value
        console.log('side effects!')
    }
    add() { this.count += 1 }
}

function App() {
    const ref = useRef()
    const click = useCallback(() => {
        ref.current.add()
    }, [ref])
    return (
        <my-counter ref={ref} count={3}></my-counter>
        <button onClick={click}>Add 1</button>
    )
}
class MyTimer extends HTMLElement {
	connectedCallback() {
		setInterval(() => {
			this.dispatchEvent(new Event('Tick'))
		}, 1000)
	}
	}
	
	function App() {
		const [ticks, setTicks] = useState(0)
		const add = () => setTicks(ticks + 1)
		return (<my-timer onTick={add}>
			{ticks}
		</my-timer>)
	}
	

HTML & (shadow) DOM


class MyButton extends HTMLElement {
    connectedCallback() {
        this.innerHTML = '<button>Click me</button>'
    }
}

class MyButton extends HTMLElement {
    connectedCallback() {
        this.attachShadow({mode: 'open'});
        this.shadowRoot.innerHTML = '<button>Click me</button>'
    }
}
Devtools shopwing the shadow root for native input elements

Baseline 2024

NEWLY AVAILABLE
<my-element>
	<template shadowrootmode=open>
		<button>Click me</button>
	</template>
</my-element>
<my-element> 
	#shadow-root (open) "Good!" 
	</my-element>
	<my-element> 
	<template shadowrootmode="open">…</template> 
	"Bad!" 
	</my-element>
	
Devtools for the my-element element.
const el = document 
	.createElement('my-element') 
const template = document 
	.createElement('template') 
template 
	.setAttribute('shadowrootmode', 'open') 
el.append(template) 
document.body.append(el)
function MyElement() {
	return (<my-element>
		<template shadowrootmode=open>
			<button>Click me</button>
		</template>
	</my-element>)
}
	
import ReactShadowRoot from 'react-shadow-root'
	function MyElement() {
		return (<my-element>
			<ReactShadowRoot>
				<button>Click me</button>
			</ReactShadowRoot>
		</my-element>)
	}
	

Baseline 2024

NEWLY AVAILABLE

<my-element>
    <template shadowroot="open">...</template>
</my-element>
<my-element>
    <template shadowroot="open">...</template>
</my-element>
<my-element>
    <template shadowroot="open">...</template>
</my-element>
<my-element>
	<template shadowrootmode="open">
		<button>Click me</button>
	</template>
	
	<h1>A GIANT HEADER</h1>
</my-element>
k91.io/mdnslot

	<my-element>
		<template shadowrootmode="open">
			<button>Click me</button>
			<slot></slot>
		</template>
	
		<h1>A GIANT HEADER</h1>
	</my-element>
	
k01.io/mdnslot
Example shows in large text 'A GIANT HEADER'

	<my-element>
		<template shadowrootmode="open">
			<p><slot>Default!</slot></p>
		</template>
	</my-element>
	
	<my-element>
		<template shadowrootmode="open">
			<p><slot>Default!</slot></p>
		</template>
		Not default!
	</my-element>
	

Baseline Widely available

	
	<my-element>
		<template shadowrootmode="open">
			<slot name=first></slot>
			<slot name=second></slot>
		</template>
	
		<p slot=second>First</p>
		<p slot=first>Second</p>
	</my-element>
	
	
Example shows 'Second' then below it 'First'.

Slide Text


	<app-layout>
		<template shadowrootmode="open">
			<slot name="user-avatar">
				<loading-spinner/>
			</slot>
		</template>
		<!-- {{await getConnection()}} -->
		<img slot="user-avatar" src="/users/{{await getUsername()}}"/>
	</app-layout>
	

Image Description

Example shows "My App" and to the right of that a spinner.

The spinner above is replaced with an avatar of Keith.

<my-element>
    <template shadowroot="open">
        <slot name="content">Empty</slot>
    </template>
    <div>
        <p slot="content">
            Nested Content
        </p>
    </div>
</my-element>
The example shows the word "Empty"

Accessibility

<tab-nav>
	<template shadowrootmode="open">
		<slot role="tablist" name="tab"></slot>
	</template>
	<button role="tab">Tab 1</button>
	<div role="tabpanel">Panel 1</div>
</tab-nav>
	

	<tab-nav>
		<template shadowrootmode="open">
			<div role="tablist">
	 			<slot name="tab""></slot>
			</div>
		</template>
		<button role="tab">Tab 1</button>
		<div role="tabpanel">Panel 1</div>
	</tab-nav>
	
<button aria-describedby="my-tooltip"></button>
	<tool-tip id="my-tooltip">
		<template shadowrootmode="open">
			<slot id="description"></slot>
			<button aria-label="Close">X</button>
		</template>
	<p>Description for button</p>
</tool-tip>

Proposal


	<button aria-describedby="my-tooltip"></button>
	<tool-tip id="my-tooltip">
		<template shadowrootmode="open"
			shadowrootreferencetargetmap="aria-describedby: description">
			<slot id="description"></slot>
			<button aria-label="Close">X</button>
		</template>
	</tool-tip>
	

Styling


	<my-element>
		<template shadowrootmode=open>
			<style>
				button {
					background: green;
				}
			</style>
			<button>Click me</button>
		</template>
	</my-element>
	
Below the code, there is a rendered version of the button with a green background

	<style>
		button {
			background: green;
		}
	</style>
	<my-element>
		<template shadowrootmode=open>
			<button>Click me</button>
		</template>
	</my-element>
	
Below the code, there is a rendered version of the button with an unstyled background.

	<link rel=stylesheet href=my-design-system.css>
	<my-element>
		<template shadowrootmode=open>
			<link rel=stylesheet href=my-design-system.css>
			<button>Click me</button>
		</template>
	</my-element>
	
A green button labeled 'Click me' is displayed beneath a block of code.

	<style>
		button {
			background: green;
		}
	</style>
	<my-element>
		<template shadowrootmode=open>
			<slot name=button></slot>
		</template>
		<button slot="button">Click me</button>
	</my-element>
	
A green button labeled 'Click me' is displayed beneath a block of code.

	const sheet = new CSSStyleSheet()
	sheet.replaceSync(`
		button {
			background: green
		}
	`)
	class MyElement extends HTMLElement {
		connectedCallback() {
			this.attachShadow({mode: 'open'})
				.adoptedStyleSheets.push(sheet)
		}
	}
	
Baseline 2023 NEWLY AVAILABLE
A green button labeled 'Click me' is displayed beneath a block of code.

Limited availability

import './styles.css' with { type: 'css' }
	class MyElement extends HTMLElement {
		connectedCallback() {
			this.attachShadow({mode: 'open'})
				.adoptedStyleSheets.push(sheet)
		}
	}
	
A green button labeled 'Click me' is displayed beneath a block of code.
class MyElement extends HTMLElement {
	connectedCallback() {
		this.attachShadow({mode: 'open'})
			.adoptedStyleSheets.push(...document.styleSheets)
	}
}
	
A green button labeled 'Click me' is displayed beneath a block of code.
class MyElement extends HTMLElement {
	connectedCallback() {
		this.attachShadow({mode: 'open'})
		for (const { cssRules } of document.styleSheets) {
			const sheet = new CSSStyleSheet();
			for (const rule of cssRules) {
				sheet.insertRule(rule.cssText);
			}
			this.shadowRoot.adoptedStyleSheets.push(sheet);
		}
	}
}
A green button in the visual element contains the text Click me.

	<link rel=stylesheet href=my-design-system.css
		media="screen, --crossroot">
	<my-element>
		<template shadowrootmode=open>
			<button><Click me></button>
		</template>
	</my-element>
	
A green button labeled 'Click me' is displayed beneath a block of code.

<style>
    :root { --my-green: green; }
</style>

<my-element>
    <template shadowroot="open">
        <style>
            button {
                background: var(--my-green);
            }
        </style>
        <button>Click me</button>
    </template>
</my-element>

A green button labeled 'Click me' is displayed beneath a block of code.

Baseline Widely available

<style>
		button, ::part(my-button) {
			background: green;
		}
	</style>
	<my-element>
		<template shadowrootmode=open>
			<button part="my-button">Click me</button>
		</template>
	</my-element>
	

A green button labeled 'Click me' is displayed beneath a block of code.


<style>
    :state(busy) { filter: grayscale(1); }
    :state(busy)::part(my-button) { cursor: not-allowed; }
</style>

class MyElement extends HTMLElement {
    #i = this.attachInternals()

    get busy() {
        return this.#i.states.has('busy');
    }

    set busy(value) {
        this.#i.states[value ? 'add' : 'delete']('busy');
    }
}
Baseline 2024 NEWLY AVAILABLE

grey button labeled 'Click me' is displayed beneath a block of code

So I should use Web Components?

Thank you

(Shout out to Mu-An (muan.co) for recommending me for this talk)