#

## What are they?

Web Components are a set of technologies that allow us to extend the standard set of HTML elements with additional elements.

The three main technologies are:

Custom elements
  A way to extend HTML so that instead of having to build all our markup out of `<div>`, `<input>`, `<span>` and friends, we can build with higher-level primitives.
Shadow DOM
  Extending custom elements to have their own separate DOM, isolating complex behavior inside the element from the rest of the page.
HTML templates
  Extending custom elements with reusable markup blocks using the `<template>` and `<slot>` tags, for quickly generating complex layouts.

Those 3 bullets tell you everything and nothing at the same time. This probably isn't the first tutorial on Web Components you've seen, and you may find them a confusing topic. However, they're not that complicated as long as you build them up step by step ...

#

## A simple component

Let's start with the most basic form, a custom element that says 'hello world!':

We can use it in a page like this:

Which outputs this page:

So what's happening here?

We created a new HTML element, registered as the `x-hello-world` tag, and used it on the page. When we did that, we got the following DOM structure:

- body (node)
  - x-hello-world (node)
    - 'hello world!' (textContent)

Explaining the code of the custom element line by line:

`class HelloWorldComponent extends HTMLElement {`
  Every custom element is a class extending `HTMLElement`. In theory it's possible to extend other classes – like `HTMLButtonElement` to extend a `<button>` – but in practice this doesn't work in Safari.
`connectedCallback() {`
  This method is called when our element is added to the DOM, which means the element is ready to make DOM updates. Note that it may be called multiple times when the element or one of its ancestors is moved around the DOM.
`this.textContent = 'hello world!';`
  The `this` in this case refers to our element, which has the full HTMLElement API, including its ancestors `Element` and `Node`, on which we can find the `textContent` property which is used to add the `'hello world!'` string to the DOM.
`customElements.define('x-hello-world', HelloWorldComponent);`
  For every web component `window.customElements.define` must be called once to register the custom element's class and associate it with a tag. After this line is called the custom element becomes available for use in HTML markup, and existing uses of it in already rendered markup will have their constructors called.

### Why is it named `x-hello-world` instead of `hello-world`?

There are custom element tag naming rules, in particular that the name must start with a lowercase letter and contain a dash. On top of that, all tags take part in a global namespace, where there is always a risk of conflicting naming. While `hello-world` would also be a valid name, in general it is best to start all custom element tag names with a unique prefix of your choice. On this site the placeholder prefix `x-` was chosen.

Another gotcha is that custom element tags cannot be self-closing. So `<x-hello-world></x-hello-world>` is ok, but `<x-hello-world />` is not.

#

## An advanced component

While the simple version above works for a quick demo, you'll probably want to do more pretty quickly:

- Adding DOM elements as children to allow for richer content.
- Passing in attributes, and updating the DOM based on changes in those attributes.
- Styling the element, preferably in a way that's isolated and scales nicely.
- Defining all custom elements from a central place, instead of dumping random script tags in the middle of our markup.

To illustrate a way to do those things with custom elements, here's a custom element `<x-avatar>` that implements a simplified version of the HeroUI Avatar component (React):

Some key elements that have changed:

- The `observedAttributes` getter returns the element's attributes that when changed cause `attributeChangedCallback()` to be called by the browser, allowing us to update the UI.
- The `connectedCallback` method is written in the assumption that it will be called multiple times. This method is in fact called when the element is first added to the DOM, but also when it is moved around.
- The `update()` method handles initial render as well as updates, centralizing the UI logic. Note that this method is written in a defensive way with the `if` statement, because it may be called from the `attributeChangedCallback()` method before `connectedCallback()` creates the `<img>` element.
- The exported `registerAvatarComponent` function allows centralizing the logic that defines all custom elements in an application.

Once rendered this avatar component will have this DOM structure:

- body (node)
  - x-avatar (node)
    - img (node)
      - src (attribute)
      - alt (attribute)

For styling of our component we can use a separate css file:

Notice that:

- Because we know what the tag of our component is, we can easily scope the styles by prepending them with `x-avatar`, so they won't conflict with the rest of the page.
- Because a custom element is just HTML, we can style based on the element's custom attributes in pure CSS, like the `size` attribute which resizes the component without any JavaScript.

An example that shows the two different sizes on a webpage:

The HTML for this example centralizes the JavaScript and CSS logic to two index files, to make it easier to scale out to more web components. This pattern, or a pattern like it, can keep things organized in a web application that is built out of dozens or hundreds of different web components.

The `<script>` tag in this example has moved into the `<head>` and picked up a `defer` attribute. This will allow it to download in parallel with the rest of the page, and have it execute right when the page is loaded.

In `index.css` the use of the `@import` keyword may seem surprising as this keyword is often frowned upon for performance reasons. However, in modern browsers over HTTP/2 and in particular HTTP/3 the performance penalty of this keyword is not that severe, especially because files included with `@import` now download in parallel.

### Don't we need a bundler?

If you've paid close attention, you have seen that we're using the ES module syntax for `import` and `export` in our code, but we haven't set up any JavaScript bundler to transpile that syntax. The magic that makes this work is in `index.html`, where the `<script>` tag's `type="module"` attribute enables ES module mode for all the included JavaScript. This is supported by all modern browsers.

#

## Adding Children

Allowing children to be added to a web component is not hard. In fact, it is the default behavior. To see how this works, let's extend the avatar example by wrapping it with a badge:

To clarify, this is the DOM structure that is created:

- div (node)
  - x-badge (node)
    - content (attribute)
    - span (node, showing content)
    - x-avatar (node)
  - input (node)

The `x-avatar` component is identical to the previous example, but how does the `x-badge` work?

Some notes on what's happening here:

`this.insertBefore`

  Care must be taken to not overwrite the children already added in the markup, for example by assigning to `innerHTML`. In this case the span that shows the badge is inserted before the child elements.

  This also means that for custom elements that should not have children, this can be enforced by calling `this.innerHTML = ''` from `connectedCallback()`.

`set content(value) {`
  Custom element attributes can only be accessed from JavaScript through the `setAttribute()` and `getAttribute()` methods. To have a cleaner JavaScript API a setter and getter must be created for a class property that wraps the custom element's `content` attribute. See the `index.html` above for where this is called.

### Detecting children

Knowing when children are added or removed to a web component is possible with a MutationObserver, by calling its observe method with `observer.observe(this, { childList: true })`. Creating it in the constructor will observe a mutation of type `childList` when the child elements are first added, and also when later on elements are added or removed.

#

## Bells and whistles

Having seen what regular web components look like, we're now ready to jump up to the final difficulty level of web components, leveraging the more advanced features of Shadow DOM and HTML templates.

This can all be brought together in this page layout example, that defines a new `<x-header>` component:

This is the code for the newly added `<x-header>` component:

There's a lot happening in `header.js`, so let's unpack.

`const template = document.createElement('template');`
  The header code starts out by creating an HTML template. Templates are fragments of HTML that can be easily cloned and appended to a DOM. For complex web components that have a lot of markup, the use of a template is often convenient. By instantiating the template outside the class, it can be reused across all instances of the `<x-header>` component.
`<link rel="stylesheet" href="${import.meta.resolve('./header.css')}">`
  Because this component uses a shadow DOM, it is isolated from the styles of the containing page and starts out unstyled. The `header.css` needs to be imported into the shadow DOM using the `<link>` tag. The special import.meta.resolve trick imports the CSS file from the same path as the `header.js` file.
`<slot></slot>`
  The `<slot>` element is where the child elements will go (like the `<x-badge>` child of `<x-header>`). Putting child elements in a slot is similar to using a `children` prop in a React component. The use of slots is only possible in web components that have a shadow DOM.
`constructor() {`

  This is the first example that uses a constructor. The constructor is called when the element is first created, but before it's ready for DOM interaction. The default behavior of a constructor is to call the parent class's constructor `super()`. So if all that is needed is the default HTMLElement constructor behavior then no constructor needs to be specified.

  The reason it is specified here is because the constructor is guaranteed to be called exactly once, which makes it the ideal place to attach a shadow DOM.

`if (!this.shadowRoot) { this.attachShadow({ mode: 'open' });`

  `attachShadow` attaches a shadow DOM to the current element, an isolated part of the DOM structure with CSS separated from the containing page, and optionally with the shadow content hidden away from the parent page's JavaScript context (if `mode: 'closed'` is set). For web components that are used in a known codebase, it is usually more convenient to use them in open mode, as is done here.

  `if (!this.shadowRoot) {` is not strictly necessary, but allows for server-side generated HTML, by making use of declarative shadow DOM.

`this.shadowRoot.append(template.content.cloneNode(true));`

  The `shadowRoot` property is the root element of the attached shadow DOM, and is rendered into the page as the `<x-header>` element's content. The HTML template is cloned and appended into it.

  The shadow DOM becomes immediately available as soon as `attachShadow` is called, which is why the template can be appended in the constructor, and why the `update()` method can be called there. For custom elements without shadow DOM rendering the element's content should be deferred until `connectedCallback()`.

All the new files of this example put together:

As you can see in `header.css`, styling the content of a shadow DOM is a bit different:

- The `:host` pseudo-selector applies styles to the element from the light DOM that hosts the shadow DOM (or in other words, to the custom element itself).
- The other styles (like `h1` in this example) are isolated inside the shadow DOM.
- The shadow DOM starts out unstyled, which is why `reset.css` is imported again.

### To shadow or not to shadow?

Because custom elements can contain children with or without a shadow DOM, you may be wondering when to use this feature.

Shadow DOM comes with a number of downsides:

- There is a performance penalty, noticeable when putting hundreds of shadow DOMs on a page.
- It can cause accessibility issues.
- While Google can crawl inside shadow DOM, other crawlers may have trouble.
- There can be a FOUC while a stylesheet from a `<link>` tag is loading. This can be worked around with constructable stylesheets, but this adds complexity.

For those reasons, if you're not making components for someone else, you may find it easier to avoid shadow DOM.

Shadow DOM is however recommended in these situations:

- Intermediate elements need to be created between the root element and the contained children, like the `<header>` in the example above. Only the use of a slot inside of a shadow DOM makes this possible.
- Multiple places in the web component will accept children. Named slots provide this ability. This can be convenient for layout components.
- The styles and DOM need to be isolated from the containing page. This is often the case for web components designed to be used by others or embedded in third-party sites.

#

## Passing Data

Everything up to this point assumes that data passed between web components is very simple, just simple numeric and string attributes passing down. A real world web application however passes complex data such as objects and arrays between parent and child components.

This example demonstrates the three major ways that data can be passed between web components:

### Events

The first way is passing events, usually from child components to their parent component. This is demonstrated by the form at the top of the example.

Every time the Add button is pressed a `CustomEvent` of type `add` is dispatched using the `dispatchEvent` method. The event data's `detail` property carries the submitted form data.

The event is handled one level up:

The `update()` method sends the updated list back down to the `<santas-list>` and `<santas-summary>` components, using the next two methods.

### Memory leak?

You may be wondering if real world code would need a `removeEventListener()` to prevent memory leaks. In most cases the answer is a surprising no, because DOM event listeners are weakly bound and do not prevent garbage collection of the thing they are listening to. If it soothes your mind, you're still free to add those remove calls anyway.

### Properties

The second way to pass complex data is by using class properties, as exemplified by the `<santas-list>` component:

The `list` setter calls the `update()` method to rerender the list.

This is the recommended way to pass complex data to stateful web components.

The best practice way of implementing attributes, properties and events is subtle and opinionated. The article The Attribute-Property Duality dives into this topic in depth, and is recommended reading when making web components that will be embedded in third-party sites or are otherwise expected to behave like built-in elements.

### Beware of XSS

Keen observers may have noticed there's a cross-site scripting bug in the above example. This can be solved by properly encoding entities for `person.name`. To see how this can be done, check out the entity encoding chapter on the Applications page.

### Methods

The third way to pass complex data is by calling a method on the web component, as exemplified by the `<santas-summary>` component:

This is the recommended way to pass complex data to stateless web components.

### Complete example

Finally then, here is all the code for the Santa's List application:

### The power of the browser

A reminder that all of this code is still vanilla. It does not make use of any framework, library or build tool and works in all the major browsers. It will never need changes to be compatible with new dependency versions. And most importantly, it remains readable and maintainable.

There's still more to say on web components. For a deeper dive, check out this article on the web component lifecycle.

## Up next

Learn about styling Web Components in ways that are encapsulated and reusable.

Add Styling