DEV Community

Cover image for The Problem with Web Components
Besworks
Besworks Subscriber

Posted on • Edited on

52 5 4 5 3

The Problem with Web Components

The real problem with Web Components is simple: There is no clearly defined road to success for using them. No singular go-to guide. Up until recently the standard has been in flux. And in some ways, still is. But the core APIs have stabilized, and many developers are jumping into the scene.

However, it can be hard for someone who hasn't followed the evolution of this standard to know where to begin. Learning the concepts necessary to use this powerful set of tools is a challenge. Many articles exist on this subject, but the real useful information is scattered, buried among outdated tutorials demonstrating experimental features that have changed over the years.

So, if you have been wondering how to get to started crafting your own components from scratch, you are reading the right article.

The Anatomy of a Web Component

Let's begin with the fundamentals. The key aspect of Web Components that make them so useful is the ability to create Custom HTML Elements. You have probably also heard of HTML Templates and Shadow DOM. These additional APIs give us power over a browser's native encapsulation methods, but they are not essential to understand just yet. Before we dive into those advanced topics, let's explore how browsers handle Custom Elements.

Understanding Custom Elements

When an HTML document is parsed, element instances are created based on the tags found. Standard elements like <div>, <p>, or <button> are well-known to browsers. But what happens when you try to create your own tag?

<component></component>
Enter fullscreen mode Exit fullscreen mode

Browsers can't go throwing a fit over every tag they don't recognize. And they can't just drop data that was meant to be delivered to the user. So they initialize these undescribed tags as instances of HTMLUnknownElement and render their contents as plain text or even continue to parse the subtree of nested HTML. This behavior is what underlies the ability to define our own elements.

Defining a Custom Element

HTML has a well defined set of tags. This set doesn't change very often. We got quite a few new semantic elements when HTML5 was released and not many since. In the past, we could only hope for new elements. Then we made complex abstractions out of <div> and other elements. Now, we can streamline those abstractions into re-usable components that add meaning to our document. But the HTML root namespace needs to be left available for future built-in tags. As a result, if you want to create your own tag, it must contain at least one hyphen -.

<my-element>Hello World</my-element>
<my-next-element>
  <span> This </span>
  <span> is </span>
  <span> a </span>
  <span> test </span>
</my-next-element>
Enter fullscreen mode Exit fullscreen mode

By doing this, we have told the browser that this is NOT an unknown element anymore. We are taking responsibility for it. This element will instead be parsed as a generic inline HTMLElement, not unlike a <span>.

You can target them with CSS, just like any other element.

my-element {
  display: block;
  padding: 1em;
  color: black;
  background: green;
}

my-next-element {
  display: flex;
  flex-flow: row wrap;
  justify-content: space-around;
}
Enter fullscreen mode Exit fullscreen mode

You can query them with JS, just like any other element.

const myElement = document.querySelector('my-element');
console.log(myElement instanceof HTMLElement); // true

const myNextElement = document.querySelector('my-next-element');
console.log(myNextElement.children); // HTMLCollection(4)
Enter fullscreen mode Exit fullscreen mode

All of the base HTMLElement interface features are available, like events.

myElement.addEventListener('click', event => {
    console.log(event.target);
})
Enter fullscreen mode Exit fullscreen mode

But we've only just scratched the surface of the world of Web Components.

Registering a Custom Element

To transform our generic element into something meaningful, we need to register it with the browser.

class MyElement extends HTMLElement {
  constructor() {
    super();
  }
}

customElements.define('my-element', MyElement);
Enter fullscreen mode Exit fullscreen mode

By itself, the example above does not provide any benefit beyond what we have already done through markup alone. But it sets up the foundation of our new modular, reusable component. Before Custom Elements existed, we would have to: Wait for the DOM to be fully parsed, query it to select all instances of some specific element, and wrap our functionality around those elements using external scripting.

With Custom Elements, we can now embed that logic directly into the element's definition. We no longer have to go searching for the elements in the DOM. Every time our element is encountered, it will automatically use our class that we have defined for it.

Harnessing The Power

Registering our element gives us the flexibility to do all sorts wizardry. We can parse the element for children, we can detect new children, we can add our own children. We can manipulate the element however we want. It really depends on your use-case. Every component is different. The possibilities are endless.

But we have to start somewhere. So let's build a simple example. We will create a <copy-text> element. When this element is clicked, it's text content will be copied to the clipboard.

We could accomplish this with some JavaScript targeting a <span>, but having a dedicated element makes the intention clear in our markup. It also lets us create unique rules in our stylesheet that do not require excessive class or ID scoping. And we don't have to change any code to add more instances of our element.

Notice how I have used #private methods inside the class definition. This allows us to protect the internal functionality of our component so that it cannot be manipulated from the outside. This will become more important later when we get into encapsulation. For now, we've created a component that lives entirely in the root document. The page's stylesheet fully covers the element. Its entire structure can be accessed from the document context.

Reusing Custom Elements

A Custom Element only needs to be initialized once per browsing context. Including our component definition either as an inline or fetched script is enough to let us use it as many times as we want, anywhere we want.

<!DOCTYPE html>
<title> Copy Text Element Example </title>
<link rel="stylesheet" href="theme.css">
<script type="module" src="copy-text.js"></script>
<div>
  <label> Example: </label>
  <copy-text title="Copy Your API Key">
    A_REALLY_LONG_API_KEY_OR_WHATEVER
  </copy-text>
</div>
<div>
  <label> Example 2: </label>
  <copy-text>
    Some more text to copy
  </copy-text>
</div>
Enter fullscreen mode Exit fullscreen mode

If we export the class from our module, we can also import it into other scripts and create custom instances.

// at end of copy-text.js
export { CopyTextElement }
Enter fullscreen mode Exit fullscreen mode
// in another component
import { CopyTextElement } from './copy-text.js';
const copyText = new CopyTextElement();
Enter fullscreen mode Exit fullscreen mode

However, this is not necessary if you are 100% confident that your module is already loaded. In that case we can simply create an instance like any other element. If needed, we can also get our class from the element registry.

const copyText = document.createElement('copy-text');
const CopyTextElement = customElements.get('copy-text');
const anotherCopy = new CopyTextElement();
Enter fullscreen mode Exit fullscreen mode

If you can't control the load order of your components, you can also wait for your element to be registered.

customElements.whenDefined('copy-text')
.then(CopyTextElement => {
  const copyText = new CopyTextElement();
});
Enter fullscreen mode Exit fullscreen mode

We Have to go Deeper

So far we've only really discussed how a Custom Element behaves on it's own. Now we need to handle an element that contains a subtree. In a legacy web app, we would select our collection of elements by targeting the host element. With a Custom Element, we can make the element itself be responsible for it's own children. The best way to handle this is with a Mutation Observer.

We set up the initial event handlers, then dynamically add or remove them from any elements that enter or leave the subtree. Notice we initialize everything in the constructor. While many tutorials focus on connectedCallback as the most important part of the lifecycle, this isn't necessarily the case. The constructor is the perfect place to set up our component's core functionality.

The main reason for this is so that our element can handle children immediately. We don't wait until it is connected to the DOM. This allows us to create instances of this element through code that are already configured before they are added to the document. In this example we only added click handlers. But we can use this for much more complex logic if necessary. Rather than delaying rendering when inserted, our element is ready to go right away.

const clickHander = document.createElement('click-handler');

[ 'test', 'another', 'more' ].forEach(label => {
  const span = document.createElement('span');
  span.textContent = label;
  clickHandler.append(span);
});

requestAnimationFrame(() => {
    document.append(clickHandler);
});
Enter fullscreen mode Exit fullscreen mode

Elegant and Flexible

We've seen how Custom Elements can enable reusable functionality in a simple, modular way. No build tools, or framework dependencies. Just meaningful markup, classic CSS, and vanilla JS. Web Components that work anywhere. But all we've done so far is streamline the methods we already had available. There is much more that this toolkit has to offer.

In the next article, we'll explore how HTML Templates can let us easily upgrade simple, declarative, definitions into complex markup structures. We'll also cover how to make the most of Shadow DOM to fully or selectively encapsulate our components.

Until then, try building your own Custom Elements. Start simple. Focus on solving real problems. Here's a few ideas to get the creative juices flowing:

  • <format-number> : Display numbers with specific formatting (currency, decimals, etc.)
  • <tool-tip> : Show some popup descriptive text when hovered or tapped.
  • <marquee-text> : A throwback to one of the earliest non-standard elements.
  • <nav-bar> : A website menu that automatically highlights the active route.

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (43)

Collapse
 
xwero profile image
david duymelinck

The first source I go to for all things HTML, CSS and javascript is MDN.
Are you alluding their web component guides are not up to date?

I think most frontend developers are too comfortable with doing everything in javascript/typescript, with an API they picked. And I think that is real problem. People invested too much time and effort in learning a third party way to create HTML.

Apart from the clickbait title, I think it is a good post!

Collapse
 
besworks profile image
Besworks

MDN is absolutely the most up-to-date resource. But the information isn't exactly laid out in a simple way that is easy for someone who doesn't know where to start.

As for the clickbait... I would apologize, but it's working exactly as intended. 😏

Collapse
 
xwero profile image
david duymelinck

Clickbait, if it ain't broken don't fix it :)

Collapse
 
mbrowne profile image
Matt Browne

If you're using web components (or just custom elements without web components) and want to do server-side rendering, definitely check out Enhance: enhance.dev/. That way you can have all the benefits of custom elements and web components, and still have the SEO and performance benefits of SSR. For simpler pages, you might not even need any client-side JS at all and can still use custom elements and slots to render your HTML.

Collapse
 
besworks profile image
Besworks

Awesome! Thanks for sharing! This looks very slick, I will definitely be giving it a test drive.

Collapse
 
mbrowne profile image
Matt Browne

Yeah and they even have a WebAssembly version that lets you integrate it into any server-side language with WASM support, so it's not limited to JS on the server.

Collapse
 
xwero profile image
david duymelinck

It is a very opinionated solution, I like my backend code a lot more malleable.

Also all the HTML I seen in the documentation is a javascript (template) string. This is something I avoid using web components.

So it is not my cup of tea.

Collapse
 
mbrowne profile image
Matt Browne

Interesting take - to each their own. There are certain aspects of how they recommend managing state and passing it to components that I didn't like that much from a DX standpoint, but you don't have to use it in that way if you don't want. At the end of the day it's mainly just a tool for SSR, so that you can use the same custom HTML elements as on the client without having to do double the work by writing your server components with some other technology. There are also other tools out there for server-side rendering custom elements for those who want an alternative to Enhance. The important thing is that unless your app is behind a login, and you know that your users will all have a fast internet connection, server-side rendering is important for optimal SEO and UX. If it weren't for that then I would just advocate for using custom elements and web components directly without any other tools.

Thread Thread
 
xwero profile image
david duymelinck

I wouldn't know what the benefit is to prerender web components and even less custom elements?

I think the most uses for web components will have a personalized output, like a user name, the content of a shopping cart. That is not relevant to score higher on search engines.

For the user experience I would think devices are fast enough to render a limited set of start data. You are not going to parse thousands of lines of json in a web component?

I think it wrong to see web components as an equivalent of React or Vue. Web components are not meant to generate full pages. They should be used when the combination of HTML, CSS and javascript becomes cumbersome.

Thread Thread
 
besworks profile image
Besworks

Well designed Custom Elements do not require pre-rendering because the content is pure markup and JS is only added for interactivity. Ideally, SSR for a web component would simply involve populating slots from a database query, like any other server generated HTML. This is most beneficial for above-the-fold content. Beyond that, constructing complex elements offscreen is no big deal, especially if you preload components that will be used on additional views.

That said, I still think Enhance is a neat concept and a great example of how these new standards are driving the development of new frameworks.

Thread Thread
 
mbrowne profile image
Matt Browne

@xwero there are a lot of benefits of a component-based architecture for building web apps—take for example a reusable Card component or Image component that might be reused many times to render the initial HTML for a page. If in your use cases you don't need any components until the user interacts with something on the client, great—and I would agree you don't need something like Enhance in that case. Enhance is very focused on progressive enhancement and the phrase they use is "HTML-first"—they really try to maximize what you can do with HTML and CSS in the initial HTML returned by the server, only introducing actual client-side web components where they're actually needed (in fact they have examples of selectively loading JS so you might not even have JS at all in some pages of your app, or others where it's just vanilla JS and it's not necessary to bring in any of Enhance's client-side libraries). I suppose you could think of it a bit like if you had server-side "components" in a language like PHP that you used to build the HTML of your page, but instead of using PHP you're using JS and syntax that matches the custom elements spec so you have nice consistency between server and client code.

Behind the scenes, the basic technique they take advantage of is the fact that you can put child elements inside of a custom element, and the visible result will be the same as if you did <my-component></my-component> and have JS code in the MyComponent class setting its innerHTML. So that's what it does to achieve server-side rendering, and then if you need to change the HTML on the client in response to some event, that's when it becomes a true web component (otherwise it just stays as a custom element with plain HTML).

This video is a good introduction for anyone interested to learn more:
youtube.com/watch?v=RaDYBFC2lHY

Thread Thread
 
xwero profile image
david duymelinck

You don't need web components to have a component based architecture. It has been done for years with classes as a hooks.
Progressive enhancement is a term I first seen after ajax was introduced in internet explorer 6.
HTML-first is a term that was invented to make frontend developers aware that using React or Vue for every website is not a good idea, in my opinion.
Components that can be used on the server and in the browser are called isomorphic components. I think is a solution in search of a problem because the tasks of the environments are different. On the server the main goal is to push the data out as fast as possible, and in the browser it is all about showing the data as fast as possible.
I'm sorry to burst your bubble but Enhance brings nothing new to the table.

I am still struggling when to use web components. I don't see a lot of difference between <my-component></my-component> and <div class="my-component"></div>. I even think the class way is more versatile.
Web components have encapsulation for CSS and javascript, but you don't need it if you target the right classes.
The only benefit I see is that they don't need a runtime like React or Vue, so they load less javascript if you do it correct.

I think web components have their place, but it our task to use them wisely.

Thread Thread
 
mbrowne profile image
Matt Browne

Well it's great then that we have so many technologies to choose from—I was never saying that Enhance was something everyone needed or everyone should use :-) I did want to correct the record though and say it does bring something new to the table for a large number of use cases. That's not something I'm going to be able to explain just in comments on a blog post, but I would just like to point out that I also remember when the term "progressive enhancement" was first talked about, and so do the creators of Enhance. The creators of it are a very mature team who have seen things develop from the early days of vanilla JS to jQuery and then all the other libraries and frameworks, trying various libraries and seeing their pros and cons (I also did this), and then creating Enhance after a lot of careful thought and consideration to fill a gap that hadn't already been filled by other approaches. I have no affiliation with the Enhance team, and TBH I've only worked on some very simple side projects with it because at my job we use React... But as someone who has been around and studied different approaches to frontend development for many years, I do think their philosophy makes sense for a lot of websites—not all of course (no silver bullets and all that)—and I have seen its benefits by playing around with it.

So based on what I have seen and compared it with, I think you are making an unfair assessment when you say it brings "nothing new to the table". That may well be true for the apps you have built, and it also might not be your cup of tea even if you were using it for a site that's an ideal use case for it. So you're entitled to your opinion but it's an opinion and not a fact that's true for everyone.

Thread Thread
 
xwero profile image
david duymelinck

I might have been too snarky with my comment. The thing I fear the most is that frameworks that go all in on web components are going to give another blow to the already tainted perception of web components. For me web components are not a React or Vue replacement, that is why I mentioned we should use web components wisely.

I agree that smart people put together the framework. But they are selling you their vision like any other framework. If you see a benefit in that vision, use it.

Thread Thread
 
mbrowne profile image
Matt Browne • Edited

BTW, it's possible that in the future, the web components spec might have more direct support to facilitate web component use cases that benefit from server-side rendering - for example, see this proposal (which is also interesting for other reasons):
github.com/WICG/webcomponents/blob...

While I don't imagine that the W3C would ever expand its scope to have official specifications for server-side technology, I think it's certainly in the realm of possibility that eventually they could have some official recommendations (not a spec but a well-documented pattern) that tools like Enhance, Lit (the server-side part), etc. could implement in a more standards-based way.

Collapse
 
pengeszikra profile image
Peter Vivo

I also like the web component, because I maked a frameworkless game development, where the main power was a single component with easy view ( and edit ) the lot of assets and idea of game.

flogon-galaxy-library

The details: dev.to/pengeszikra/javascript-grea...

So I upvote the work with web components, fare less resource need for that. Save the World!

Collapse
 
daj_bry_b5f4720ecbff83f43 profile image
Daj Bry

This is right on time! I was literally looking for meaningful tutorials on this, and rather quickly came to the same conclusion. This is a very well written article with great explanations and examples. I'm really not a fan of frameworks because they abstract away exactly this part of the process. It keeps developers from being true JavaScript devs and pumps out react masters or angular etc. When they're just leveraging the core functionality that's been provided. It just takes a little time to learn that as opposed to using someone else's syntactic sugar. I'm sure people will defend their framework of choice, and that's ok. I'm not here to debate if they're useful, because obviously every job posting wants you to know at least 2.

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

pretty cool seeing web components get broken down like this - stuff always sticks with me better when i see real examples up close. ever notice if you learn faster by messing around with new code or by reading docs first?

Collapse
 
besworks profile image
Besworks

Having good docs definitely helps to understand how things should work. But I love to experiment and push things to their limits. And I originally learned JS by dissecting websites decades ago. We didn't have tutorials or good documentation back then. Trial and error or getting inspired by someone else's idea was really the only way learn.

Collapse
 
nevodavid profile image
Nevo David

Pretty cool seeing a real guide that’s not all hype or out of date. Stuff like this actually helps me get past the messy intro stage.

Collapse
 
biomathcode profile image
Pratik sharma

hmm, the article title makes it look like the author will critic web components, but didn't. lol

Collapse
 
besworks profile image
Besworks

😉

Collapse
 
dannyengelman profile image
Danny Engelman • Edited

The real problem with Web Components is simple

You fall into the same trap, you explain Web Components
by explaining its technology.

Every Web Components course teaches about "the car"
by explainining how the engine works.

I haven't seen a Web Components course yet that starts with the WHY

A car is about the journey you can take.

Similar, a Web Components course should begin with
10 distinct examples what Web Components can do.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

If the intended audience is people who are already using component frameworks, then that's kind of like selling a car to someone who already owns a bicycle: they know what they'd want it for, they just aren't convinced that it's gonna be better at it than what they already have.

Boring people with examples of why they would want a thing they're already using is only going to make them stop reading before they get to the part that matters.

Collapse
 
besworks profile image
Besworks • Edited

I haven't seen a Web Components course yet that starts with the WHY

@dannyengelman You know this is Part 3 in the series. You have commented on both previous parts. I have already covered the "why" aspect, quite thoroughly.

Boring people with examples of why they would want a thing they're already using is only going to make them stop reading before they get to the part that matters.

@darkwiiplayer Exactly. This is meant to be a practical tutorial for those who are already interested. Part 1 was a total hot-take which weeded out those who are not and enticed those who may have been on-the-fence.

Collapse
 
dannyengelman profile image
Danny Engelman • Edited

If the intended audience is people who are already using component frameworks, then that's kind of like selling a car to someone who already owns a bicycle: they know what they'd want it for, they just aren't convinced that it's gonna be better at it than what they already have.

Put the cyclist in the Ford Mustang driver seat.
Give them only 7 lines of code they can immediatly play with
I was sold on HTML in 1994 with only <H1> (I was a Gopher developer)

You don't sell Web Components to useEffect nerds by showing them MutationObserver.
You don't sell Web Components by showing MutationObserver.

Boring people with examples of why they would want a thing they're already using is only going to make them stop reading before they get to the part that matters.

Totally true.
If Web Component teachers can't come up with those 7 lines of code, the technology or them teachers suck.

Where is the Mustang?

And yes, because "Web Components" encompass multiple technologies, the teacher probably needs to come up with multiple scenarios.
But still max 7 lines, for lets say, 7 scenarios.

Thread Thread
 
besworks profile image
Besworks • Edited

Where is the Mustang?

You're analogy of car vs engine is flawed. If I were discussing the engine I would be describing the implementation details of how browsers make these features work.

What I have done here is hand over the keys to a car and showed the basics of starting the engine. Nothing more. You don't head straight onto the highway when first getting behind the wheel.

In the previous article I discussed some well established component libraries. If experienced readers want to jump right in and get rolling they have options to do so. The rest of this series will be for those who want to learn the core concepts behind this set of tools in order to fully master them.

Thread Thread
 
dannyengelman profile image
Danny Engelman • Edited

Here in NL you will be on the highway in your first lesson at 16.
BUT, there is a certified instructor in the car, who has double controls.

Same can be done online for Web Components
<html-textarea>, <css-textarea>, <javascript-textarea> that drive a resulting <iframe>
Entries monitored, layered feedback, maybe some AI?

I get the message, I have to put my money where my mouth is....

just waiting for Codex to do the work:
"Create a Web Component Tutor with 0 dependencies, only Web Components"

Collapse
 
frickingruvin profile image
Doug Wilson

Thanks for this. I'm building a Web Component myself and have struggled with a lot of it. Good to know I'm not alone. ;)

Collapse
 
nadeem_zia_257af7e986ffc6 profile image
nadeem zia

Interesting to read

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more

Tiugo image

Fast, Lean, and Fully Extensible

CKEditor 5 is built for developers who value flexibility and speed. Pick the features that matter, drop the ones that don’t and enjoy a high-performance WYSIWYG that fits into your workflow

Start now