DEV Community

Cover image for Create Reusable Web Components in HTML
Anuradha Aggarwal
Anuradha Aggarwal

Posted on • Updated on • Originally published at anuradha.hashnode.dev

Create Reusable Web Components in HTML

Ever think of creating a webpage using HTML with a simple Header and Footer? That's easy, right?

But what if your application grows and you need to repeat the same code for the header and footer 5, 10, or say 15 times?

cry.gif

Remember the DRY (Don't Repeat Yourself) principle of software development.

1_rAqvkElSismRQsJvEeuh0g.png

With the introduction of Web Components, it becomes easy to solve this problem and to create reusable HTML components.

In this article, we'll learn about Web Components in-depth, the easiest way to create custom HTML elements.

What are Web Components?

It is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.

It consists of three main technologies:

  1. HTML templates : The "template" and "slot"
    elements enable you to write markup templates that are not displayed on the rendered page. These can then be reused multiple times as the basis of a custom element's structure.

  2. Custom elements : A set of JavaScript APIs that allow you to define custom elements and their behavior, which can then be used as desired in your user interface.

  3. Shadow DOM : A set of JavaScript APIs for attaching an encapsulated "shadow" DOM tree to an element — which is rendered separately from the main document DOM — and controlling associated functionality.

In this article, we'll discuss about the Shadow DOM implementation.

Shadow DOM refers to the ability of the browser to include a subtree of DOM elements into the rendering of a document, but not into the main document DOM tree.

It allows hidden DOM trees to be attached to elements in the regular DOM tree — this shadow DOM tree starts with a shadow root, underneath which can be attached to any elements you want, in the same way as the normal DOM.

shadow-dom.png

There are some terminologies related to shadow DOM :

  • Shadow host: The regular DOM node that the shadow DOM is attached to.
  • Shadow tree: The DOM tree inside the shadow DOM.
  • Shadow boundary: the place where the shadow DOM ends, and the regular DOM begins.
  • Shadow root: The root node of the shadow tree.

Let's understand this with a simple example:-

Step 1: Create a Class Definition

To begin with, in our header.js file we define a class called Header, which extends HTMLElement:

class Header extends HTMLElement {
  constructor() {
    // Always call super first in constructor
    super();

    // write element functionality in here
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Inside the class definition, we define the element's constructor, which defines all the functionality the element will have when an instance of it is instantiated.

Step 2: Create Shadow Root

We first attach a shadow root to the custom element:

// Create a shadow root
const shadowRoot = this.attachShadow({ mode: 'open' });
Enter fullscreen mode Exit fullscreen mode

There are two options for 'mode' : 'open' * & *'closed'.

mode: open means that you can access the shadow DOM using JavaScript written in the main page context.

If you attach a shadow root to a custom element with *mode: closed * set, you won't be able to access the shadow DOM from the outside — myCustomElem.shadowRoot returns null.

Step 3: Creating the Shadow DOM Structure

Next, we use some DOM manipulation to create the element's internal shadow DOM structure:

const headerTemplate = document.createElement('template');
headerTemplate.innerHTML = `

<div>
    <div class="header">
        <h1> Header - My First Blog on Web Component </h1>
    </div>
</div>`
Enter fullscreen mode Exit fullscreen mode

Step 4: Attaching the shadow DOM to the shadow root

The final step is to attach all the created elements to the shadow root.
connectedCallback runs each time your custom element is inserted into the DOM.

connectedCallback() {
        const shadowRoot = this.attachShadow({ mode: 'closed' });
        shadowRoot.appendChild(headerTemplate.content);
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Styling the shadow DOM

After that we create a style element and populate it with some CSS to style it:

const headerTemplate = document.createElement('template');
headerTemplate.innerHTML = `

<style>
    .header{
        text-align: center;
    }
    h1{
        color: blue;
    }
</style>

<div>
    <div class="header">
        <h1> Header - My First Blog on Web Component </h1>
    </div>
</div>
`

Enter fullscreen mode Exit fullscreen mode

In the above example, we apply a style to the Shadow DOM using a style element, but it is perfectly possible to do it by referencing an external stylesheet from a "link" element instead.

const headerTemplate = document.createElement('template');
headerTemplate.innerHTML = `

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
<link href="css/style.css" rel="stylesheet">

Enter fullscreen mode Exit fullscreen mode

Your resultant header.js file will look like this:

const headerTemplate = document.createElement('template');
headerTemplate.innerHTML = `

<style>
    .header{
        text-align: center;
    }
    h1{
        color: blue;
    }
</style>

<div>
    <div class="header">
        <h1> Header - My First Blog on Web Component </h1>
    </div>
</div>
`

class Header extends HTMLElement {
    constructor() {
        // Always call super first in constructor
        super();
    }

    connectedCallback() {
        const shadowRoot = this.attachShadow({ mode: 'open' });
        shadowRoot.appendChild(headerTemplate.content);
    }
}

customElements.define('header-component', Header);
Enter fullscreen mode Exit fullscreen mode

Step 6: Import your component into HTML file

Create an index.html file and add your custom header component to it.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Components</title>
    <script src="header.js"></script>
</head>

<body>
    <header-component></header-component>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Now run index.html in browser:

image.png

image.png

Congrats!! you have created your first custom HTML component.

fun.gif

Thank you for reading. This is the first time that I wrote any blog article. I hope you enjoyed reading it.
Please share it with your network. Don’t forget to leave your comments below.

Buy-me-a-coffee

Discussion (34)

Collapse
konrud profile image
Konstantin Rouda

I'd say that learning Web Components it's much easier than learning any new Framework/Library.
I've even made a few myself.
Here is my <switch-component></switch-component>

Collapse
maciejcieslik profile image
Maciej Cieslik

And what about SEO? Funny as it is but not practical in real life.

Collapse
bennypowers profile image
Benny Powers 🇮🇱🇨🇦

Google executes JavaScript and has no problem parsing your content even if it's in shadow DOM.

But you don't have to put your content in shadow root, in fact, for many applications, it's better to keep content in the light DOM of the document.

Collapse
maciejcieslik profile image
Maciej Cieslik

Ok - Google reads and executes js, but Bing and other crawlers mostly not, so as i really want this to be prod solution i think it is just a cool experiment or usable only if website or app will have traffic ONLY from google (and this is far from reality).

Thread Thread
bennypowers profile image
Benny Powers 🇮🇱🇨🇦 • Edited

Bing executes JS. Bing crawls shadow DOM content, just like Google does.

Thread Thread
maciejcieslik profile image
Maciej Cieslik

Executing js is not the same as reading content corectly - i don't remember where, but i saw tests and bing readed component, but without semantic - if i'm incorrect about this, someone please correct me.

Would be nice if you show here tests with all the biggest search engines showing, this method is not impacting seo at all.

Thanks in advance.

Collapse
milabron profile image
milabron • Edited

Example? I ask this to see if there is anything special to do when you are not using shadow

Thread Thread
milabron profile image
milabron • Edited

I have other doubt it would not have been better to put the template in a method of the Header class, which returns the element in question, especially to have an order, do it as it is now does not give me any help to organize the code. And it's as if it was the same code of all life, even worse because it puts it in a shadow, to which I guarantee you Google doesn't have access.

Thread Thread
bennypowers profile image
Benny Powers 🇮🇱🇨🇦

a shadow, to which I guarantee you Google doesn't have access.

I wouldn't take that guarantee. Google search absolutely, 100%, without a doubt DOES read content in shadow DOM. Bing as well.

I have other doubt it would not have been better to put the template in a method of the Header class

If you're using a <template> element, then the template should either be in module scope or a static member of the class. If you make it an instance field or method, you will lose the performance benefit of only parsing the HTML once.

And this is in fact how FASTElement does it. LitElement however lets you define a render method on the instance, which will perhaps be more suitable for you.

Collapse
pamelajohns profile image
Pamelajohns

Yup! SEO is quite cool. Though it demands patience & handwork, but I suppose, it rewards in the end, as I have got from here and there! Being a busy mom, I am thinking to raise a few SEO projects!

Collapse
mjgs profile image
Mark Smith

Thanks for the well written article.

Why is it better to use web components than just vanilla javascript?

Collapse
bennypowers profile image
Benny Powers 🇮🇱🇨🇦

Web components are vanilla JavaScript

Collapse
mjgs profile image
Mark Smith • Edited

Yes indeed they are. But why are web components better than just using other existing javascript primitives like functions / classes etc ?

Thread Thread
bennypowers profile image
Benny Powers 🇮🇱🇨🇦

Well, "better" for what? If you want to calculate a sum from a list - don't write a web component, use reduce or loop. But if you want to create reusable custom HTML elements to progressively enhance your content, web components are spot on.

Alternately, if you're building a single-page web app and want to organize your code into UI components, I believe you'd be hard pressed to find a better primitives to build on than custom elements, shadow DOM, and es modules.

Why build with components? It's the state-of-the-art and a well regarded UI engineering technique in many systems not just on the web.

Thread Thread
mjgs profile image
Mark Smith

Thanks for your reply.

Yes that’s sort of what I am asking. Lots of things are ‘state-of-the-art’, but that doesn’t tell me anything. Certainly feels like they could be a great primitive, yet there isn’t much uptake from the dev community. It’s not a criticism, just describing what I see.

I’m trying to get a handle on what type of situations are well suited to web components. Reusability is a clear benefit. That sounds great.

I’m also interested to see how they enable/promote progressive enhancement. Anything there would be awesome.

Thread Thread
bennypowers profile image
Benny Powers 🇮🇱🇨🇦 • Edited

there isn’t much uptake from the dev community.

Apple, Aqovia, EA, Forter, GitHub, Google, IBM, ING, Maersk, Microsoft, Mozilla, Nintendo, Rabobank, Red Hat, Salesforce, SpaceX, VMware, Williams-Sonoma and others all ship web components.

chromestatus.com/metrics/feature/p...

10% of all Chrome page loads used custom elements

just describing what I see.

The perception among developers that React is king and web components aren't at all used is just twitter noise, nothing more.

Thread Thread
mjgs profile image
Mark Smith • Edited

I wouldn’t be a good developer and engineer if I didn’t point out the irony of you brushing off the ‘common view’ on frontend frameworks as Twitter noise, right after justifying your point of view using a Twitter thread.

However having said that, I do note that the fellow you quoted does have a very well written and convincing blog post covering reasons to use web components:

log.rockerest.com/post/why-i-use-w...

I’d like to see some articles from the engineering teams of these companies that are apparently using web components. Why aren’t they talking about it?

It’s odd that so many big companies are using them, yet there are close to zero job adverts looking for devs with web components experience. I have been looking at A LOT of job adverts recently so I know that to be the case.

Thread Thread
scott_yeatts profile image
Scott Yeatts

I've used them for about 2 years and LOVE them (to the point I'm getting self-concious that all of my comments are about web components... It's NOT a silver bullet! Sometimes old-school frameworks are useful too, just like sometimes you might find a reason to use jQuery in this day and age)

The problem may be that they're all using web components under the hood in various helpers: Stencil is my flavor of component compiler (built by the Ionic team... The reason Ionic v4+ is multi-framework and not just Angular? Web components)

Salesforce has been using web components for almost 6 years: Lightning Web Components.

Apple used Stencil in part of Apple Music (among other things), but it's just a part of the app. Github's components are called Catalyst, and the web components re-imagining of Bootstrap is called shoelace.

This stuff is all over the place, but sometimes it's realizing the tech being used behind the scenes, because the selling point isn't necessarily the tech involved, but the flexibility, and in some cases the creator's intent is to supplement the current status quo of frameworks, not upend it, so in some instances they might not even make it clear it's built on the Web Components spec. So the job post might SAY "Ionic" or "Salesforce" or what have you, but what that MEANS is web components.

That said, the Ionic team has been very active on Twitter and blogs talking about how important their decision to run with web components was.

I don't build Ionic apps, but man have I loved Stencil.

Thread Thread
bennypowers profile image
Benny Powers 🇮🇱🇨🇦

That's a great point! Not many devs write blog posts about querySelector, but that doesn't mean it's "unpopular" or to be avoided.

Thread Thread
mjgs profile image
Mark Smith

Perhaps we should just start calling them secret web components from now on.

These secret web components sure sound great.

Collapse
tufik2 profile image
Tufik Chediak Sanchez • Edited

I invite you to checkout this small component class that give reactive functionality to the standard implementation based in state (avoild re-render all component and only update the specific node when is required).

npmjs.com/package/cuppa-component

Advantages.

  1. Performance, use realDOM instead virtualDOM, also is vanilla javascript and starndar code.
  2. You can create your component library and re-use that after in any framework (react, angular, svelte, etc) due is pure javascript, no dependencies.
  3. No needed transpile code or make any configuration.
  4. Sizing.
Collapse
scott_yeatts profile image
Scott Yeatts

Excellent breakdown. One of the most interesting things I've been looking at for the last couple of years is the slow shift to a compiler pattern in JS frameworks. Svelte, Stencil from Ionic and github just came out with one recently called Catalyst that looks a lot like Stencil without JSX on a cursory inspection. Except for Svelte (where it's optional) they compile to web components.

Angular and Vue have options to compile into web components, Salesforce has been using lightning web components since 2016 (I think it was 2016), Red hat is moving the next version of Patternfly to WC, Apple's been using WC in a few places since 2018, the list goes on. I see a lot of comments about how people "don't see it"... But they would have to be actively ignoring it or in <insert top 3 framework here>-only land for that to be true.

Keep in mind, Web components have only been adopted into Firefox since Oct of 2018, and Edge since it went Chromium, so it's still early days for the spec, but it definitely looks like the trend is moving towards web components/native JS sent to the user's browser and ever so slightly away from sending a big framework to act as a runtime. But that shift takes time and articles like this to get the word out!

Web components also have a terrible reputation from around 2015 when the v0 spec was out and polymer and a few other projects we're trying to implement them. It's also where things like "Shadow DOM is bad for SEO" come from. It's outdated info. It got much better with v1 and the new generation of tools like the ones I mentioned plus lit-element and lit-html (the "new" polymer from Google)

Collapse
dannyengelman profile image
Danny Engelman

"Keep in mind, Web components have only been adopted into Firefox since Oct of 2018, and Edge since it went Chromium, so it's still early days for the spec,"

This is a funny argument.
So something is a JS standard, but a JS standard has to mature like wine or whiskey before you can use it?

Collapse
scott_yeatts profile image
Scott Yeatts

Not really an "argument" so much as a signal, or setting expectations. A common argument against using WCs that I've encountered since I started using them back in 2018 was the lack of tools and libraries compared to the current framework ecosystem.

The difference is that those frameworks have had a decade+ of tooling behind them.

The same thing happened when transitioning the ecosystem away from jQuery, as a lot of engineers at the time argued against moving away because of the huge plugin ecosystem and tools like Bootstrap that depended on jQuery.

Just like jQuery could be used inside a framework environment (and still is in some environments to this day), WCs can be used inside the traditional framework environments.

But if people allow that kind of FUD to stop them from adopting new changes to the spec, the ecosystem will never grow, and we'll still be building the same old React applications 20 years from now instead of pushing the discipline further and taking advantage of tools that are natively supported in the browser.

So in a way, from the perspective of adoption and support, my answer is that yes, there is a certain amount of maturing that anything introduced into the JS (or any other language's) ecosystem needs.

Even the spec went through two drafts before adoption, but now that it HAS been adopted, it will take a while for the ecosystem to build.

That said, the difference in support from 2018 (where there was little to none) and today is a pretty significant change, and I expect it to keep growing over the next few years.

Thread Thread
dannyengelman profile image
Danny Engelman

But if you think of Web Components as just a language feature...

No one is saying, well... Set and Map have only been possible for N years, so be careful using them...
and no one is NOT using them because there is no extra tooling.

Biggest problem with WCs is that most developers are comparing them to other technologies.
Comparing them to RAVS (React,Angular,Vue,Svelte) is like comparing Set/Map to Redux

And (maybe) an even bigger problem IS the tooling available; the number of "Web Component" libraries is just crazy. Means most people learn A Tool instead of The Technology.... and a fool with a tool, is still a fool (is what we learned from jQuery)

𝘞𝘦𝘣 𝘊𝘰𝘮𝘱𝘰𝘯𝘦𝘯𝘵𝘴 𝘢𝘳𝘦 𝘯𝘦𝘸 𝘑𝘢𝘷𝘢𝘚𝘤𝘳𝘪𝘱𝘵 𝘭𝘢𝘯𝘨𝘶𝘢𝘨𝘦 𝘧𝘦𝘢𝘵𝘶𝘳𝘦𝘴 𝘸𝘪𝘵𝘩 100% 𝘮𝘰𝘥𝘦𝘳𝘯 𝘣𝘳𝘰𝘸𝘴𝘦𝘳 𝘴𝘶𝘱𝘱𝘰𝘳𝘵,
𝘵𝘩𝘦𝘺 𝘢𝘳𝘦 𝘩𝘦𝘳𝘦 𝘵𝘰 𝘴𝘵𝘢𝘺 𝘧𝘰𝘳 𝘢𝘴 𝘭𝘰𝘯𝘨 𝘑𝘢𝘷𝘢𝘚𝘤𝘳𝘪𝘱𝘵 𝘪𝘴 𝘢𝘳𝘰𝘶𝘯𝘥.

Should be the only sentence we write to newbies.

Collapse
adham123 profile image
adham-123

So basically it's similar to what you can do in reactjs components. But without using reactjs. Though i think reactjs is better for this task.

Collapse
tufik2 profile image
Tufik Chediak Sanchez • Edited

Yes, but if you like the reactjs concept of fast implementation, don't update the DOM manually and made update automatically only when it is required or for any reason requires use vanilla js, can get advantage of that... Also there is another uses cases:

  1. Faster performance, use realDOM instead virtualDOM, also is vanilla javascript and starndard code.
  2. You can create your component library and re-use that after in any framework (react, angular, svelte, etc) due is pure javascript, no dependencies.
  3. No needed transpile code or make any configuration.
  4. Sizing.
Collapse
pjmantoss profile image
PJ Mantoss

Why can't I just use Reactjs for this?

Collapse
bennypowers profile image
Benny Powers 🇮🇱🇨🇦

You can. No one is saying you can't.

bundlephobia.com/result?p=react-do...

But if you do, you'll be shipping 40kb+ of JS over the wire just to do something the browser already comes with built-in.

What's more, "react" usually comes along with a bunch of non-standard extras, and when you inevitably have to rewrite your app down the line for compatibility, you'll be stuck under a mountain of jsx, webpack, and babel cruft.

If you write your app using web components, each element is self-contained and interfaces with the rest of the app using common HTML and DOM. You'll be able to update or change your elements piecemeal, one-by-one. You'll also be able to use your elements across frameworks easily

custom-elements-everywhere.com/

Collapse
pjmantoss profile image
PJ Mantoss

I see!
Thanks

Collapse
andericsilva profile image
Anderson Ismael Couto da Silva • Edited

interesting, but how to inject parameters and content inside the created elements?

Collapse
bennypowers profile image
Benny Powers 🇮🇱🇨🇦

Using the DOM

element.someProperty = [{...}]
Enter fullscreen mode Exit fullscreen mode

See my post for more info dev.to/bennypowers/lets-build-web-...

Collapse
dannyengelman profile image
Danny Engelman

I don't understand this line:
// Always call super first in constructor
Is there a limitation in using JS Classes for Web Components?

Collapse
eerk profile image
eerk

It's just because the keyword extends HTMLElement is used. That means you have to call super() as well.