Building web components with WebC in vanilla JavaScript
Tags: javascript, webdev
Published: 2023-01-10 17:47:23

**Written by [Iskander Samatov](https://blog.logrocket.com/author/iskandersamatov/)✏️**

_Table of contents:_

*   [What are web components?](#what-are-web-components)
*   [Why use web components?](#why-use-web-components)
    *   [Web component specifications](#web-component-specifications)
    *   [Limitations of web components](#limitations-web-components)
*   [How WebC helps](#how-webc-helps)
*   [Building custom web components with WebC in a vanilla JavaScript project](#building-custom-web-components-with-webc-vanilla-javascript)
    *   [Using the `webc:scoped` attribute in `style` tags](#using-webc-scoped-attribute-style-tags)
    *   [Adding JavaScript functionality to our web component](#adding-javascript-functionality-web-component)
*   [Progressive enhancement with WebC](#progressive-enhancement-webc)

## What are web components?

Web components are custom, reusable, and strongly-encapsulated HTML components that are library-agnostic and can be used in any JavaScript project. 

One example of a popular web component is the `<video>` tag. While `video` might seem like a single HTML element, underneath, it consists of several HTML elements and custom logic that defines its behavior.

## Why use web components?

So, why use web components? At a high level, the two main advantages of web components are encapsulation and a lack of external dependencies. 

Web components solve the encapsulation problem by allowing you to limit the impact of your CSS and JavaScript code to the scope of your component. 

Of course, popular JavaScript frameworks like React and [Angular](https://blog.logrocket.com/tag/angular) help you achieve similar encapsulation effects. So then, what makes web components special? That question brings us to the second advantage: web components are library-agnostic, and you can create them using only inbuilt JavaScript APIs. 

The library-agnostic nature of web components is especially useful when designing a UI component library. Unlike other libraries built for specific frameworks, you can use web components to build a library that isn’t coupled to a single technology stack. That means anybody can use it, regardless of the JavaScript framework they use for their project.

### Web component specifications

Typically, you will use three specifications/technologies when creating web components:

*   [Custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements): An API to create HTML elements to encapsulate your custom HTML, JavaScript, and CSS code
*   [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM): An API to attach a hidden DOM to an element that does not clash with CSS or JavaScript code in other parts of your application
*   [Templates](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots): An API for reusing a markup structure. Using the `<template>` tag, you can define a markup structure that’s not immediately rendered upon page load but is meant to be duplicated using JavaScript

To learn more about these specifications and how web components compare to JavaScript frameworks like React, check out this [LogRocket blog](https://blog.logrocket.com/react-vs-web-components/) post.

### Limitations of web components <a name="limitations-web-components">

Just like any technology, web components have limitations. Let's cover them briefly. 

Web components require browsers to allow running JavaScript on your web app, even if your web component is purely presentational and does not contain any interactive JavaScript code. 

Typically, you would need to write your web components in an imperative fashion, rather than a declarative one. That results in a somewhat clunky authoring experience, especially when implementing more advanced techniques, like progressive enhancement. We’ll cover this in more detail in a later section.

## How WebC helps

As you’ve probably guessed, WebC helps with some of the pains when working with web components. 

[WebC](https://github.com/11ty/webc) is a serializer that generates markup for web components. It’s framework-agnostic and provides various useful compilation tools that make writing web components easier. 

As I mentioned, web components normally require JavaScript to be available, even for the web components that don’t have any JavaScript in them. But with WebC, this is not the case. You can create HTML-only web components that will render even if the browser has JavaScript disabled. That’s because WebC takes your web component code and compiles it to a simple HTML output. 

WebC provides a better authoring experience when writing web components. With WebC, you can create single-file web components that are easy to write and maintain. They consolidate your HTML, CSS, and JavaScript code and simplify the process using features like templates and Shadow DOM. 

And that's just scratching the surface: WebC has more features and customizations that make writing web components a more pleasant experience. Check out [their docs](https://github.com/11ty/webc) for a more detailed overview of all the features and options.

## Building custom web components with WebC in a vanilla JavaScript project <a name="building-custom-web-components-with-webc-vanilla-javascript">

Let's see WebC in action! For this tutorial, we’ll build a custom component with a button and a box that rotates when you click the button. This is a pretty contrived example, but it will still help us illustrate the benefits of using WebC. ![Our custom button component that rotates on click](https://blog.logrocket.com/wp-content/uploads/2022/12/button-component-with-rotate-click.png) 

To follow along, first, you will need to set up a JavaScript project. I will use [Vite](https://vitejs.dev/) to spin up a new project quickly. 

You will also need Node.js installed; we will use it to run our WebC compilation script that will generate static files which Vite will serve. 

Once you’ve set up your project, run `yarn add @11ty/webc` to install WebC. Next, let’s add our `main.js` script that will register the WebC page and write its content to `index.html` at the root of our project:

```javascript
import { WebC } from "@11ty/webc";
import fs from "fs";
let page = new WebC();
page.defineComponents("components/**.webc");
page.setInputPath("page.webc");
let { html, css, js, components } = await page.compile();
fs.writeFile("./index.html", html, (err) => {
  if (err) {
    console.log({ err });
  }
});
```

So, here’s what's happening above:

*   First, we instantiate a WebC page object. It's the page that will contain our custom web component
*   Using `page.setInputPath("page.webc");`, we define the content source for our page. WebC pages and components usually have a `.webc` file extension
*   Using `page.defineComponents("components/**.webc");`, we define the folder where we will keep our web components
*   We call `page.compile()` to aggregate and compile the page's content into a static HTML output
*   Finally, we write the output HTML to the `index.html` file

Before we go any further, let's briefly cover the difference between pages and components. In WebC, pages are files that start with `<!doctype` or `<html` and are typically used to display the whole page. 

Components are any other WebC files that are used to display reusable UI pieces or fragments. 

Now, let’s create our `page.webc` file and try running our script. The file will contain a basic HTML page setup:

```html
<!-- HTML-->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebC tutorial</title>
  </head>
  <body>
    <my-component></my-component>
  </body>
  <style>
    body {
      padding: 20%;
    }
  </style>
</html>
```

You might have noticed that this looks just like our standard HTML, and that’s because it is! With WebC, you use regular HTML, JavaScript, and CSS, apart from a few cases where you’d need to add some WebC syntax. 

Now let’s try running our script. You can run it simply using the `node main.js` command. For a better developer experience though, I recommend [using nodemon](https://blog.logrocket.com/nodemon-tutorial-automatically-restart-node-js-apps-with-nodemon/). It will pick up any changes you make to your script and automatically re-run the file. 

Here's the nodemon command I'm using for this tutorial:

```javascript
nodemon main.js -e js,webc,html
```

Next, let’s add our web component by creating the `components` folder and placing the `my-component.webc` file there. Let's add the following content:

```html
<!-- HTML-->
<div id="rectangle"></div>
<button id="flipper">Flip</button>
<style webc:scoped>
  #rectangle {
    background-color: skyblue;
    width: 40px;
    height: 160px;
    border: 2px #4ba3c6 solid;
  }

  button {
    margin-top: 12px;
    width: 40px;
  }
</style>
```

Again, we’re using simple HTML and CSS to set up and style the button and the `div` for our box.

### Using the `webc:scoped` attribute in `style` tags <a name="using-webc-scoped-attribute-style-tags">

One notable part from the example above is the `webc:scoped` attribute in our `style` tag. This tag allows you to encapsulate your CSS code within your component. 

When you add this attribute to a `style` tag, WebC will automatically generate and assign a unique hashed string class to your component element during the compilation. It then prefixes that hash string in front of all of the CSS selectors you declare inside your component. 

Popular styling libraries like `emotion` and `styled-components` use a similar hash-generating mechanism to contain styling within custom components.

### Adding JavaScript functionality to our web component <a name="adding-javascript-functionality-web-component">

So far, we have defined how our web component looks, but it doesn’t do much. Let’s add some JavaScript code for interactions. 

In the same web component, right below the `style` tag, add a `script` tag with the following JavaScript code:

```html
<!-- HTML-->
<script>
  let deg = 0;
  document.getElementById("flipper").onclick = () => {
    const rectangle = document.getElementById("rectangle");
    const nextDeg = deg === 0 ? 90 : 0;
    rectangle.style.transform = `rotate(${nextDeg}deg)`
    deg = nextDeg;
  }
</script>
```

Here we’re simply adding an `onclick` listener to our button that rotates the rectangle by updating the `transform` style attribute. Notice that we didn’t have to define our web component globally using `customElements.define`; we simply added the JavaScript code to power our button. 

And that’s all there’s to it. Now we have a functioning, albeit simple web component. If you were to [turn off JavaScript for your local server](https://chrome.google.com/webstore/detail/disable-javascript/jfpdlihdedhlmhlbgooailmfhahieoem?hl=en) using an extension, our web component would still be displayed, although the button won’t do anything.

## Progressive enhancement with WebC <a name="progressive-enhancement-webc">

Another powerful feature of WebC is simplifying the process of adding progressive enhancements to your app. 

[Progressive enhancement](https://blog.logrocket.com/understanding-progressive-enhancement/) is a pattern that allows the users to access the basic content and functionality of a website first. Afterward, if the user's browser features and internet connection allows, usersl receive UI enhancements and more interactive features. 

Let’s add a simple progressive enhancement treatment to our component by disabling the button until JavaScript is available. ![Our button is disabled until JavaScript is available](https://blog.logrocket.com/wp-content/uploads/2022/12/button-disabled-javascript-available.gif) 

First, we need to update the code in our `script` tag:

```html
<!-- HTML-->
<script>
  let deg = 0;
  class MyComponent extends HTMLElement {
    connectedCallback() {
      document.getElementById("flipper").onclick = () => {
        const rectangle = document.getElementById("rectangle");
        const nextDeg = deg === 0 ? 90 : 0;
        rectangle.style.transform = `rotate(${nextDeg}deg)`
        deg = nextDeg;
      }
    }
  }
  window.customElements.define("my-component", MyComponent)
</script>
```

Here, we used the standard pattern for defining a web component using `window.customElements.define` in order to be able to wait for JavaScript to become available. 

Now let’s add the CSS treatment to our button:

```css
<!-- HTML-->
<style webc:scoped>
  #rectangle {
    background-color: skyblue;
    width: 40px;
    height: 160px;
    border: 2px #4ba3c6 solid;
  }

  button {
    margin-top: 12px;
    width: 40px;
  }

  :host:not(:defined)>button {
    opacity: 0.2;
    cursor: not-allowed;
  }
</style>
```

As you can see, we’re using the `:host:not(:defined)` selector. This selector comes from the Shadow DOM API and allows you to define special styling for your web components while they are loading. 

WebC does put its own spin on it, though: when your code is compiled, it will substitute the `:host` part with the generated hash string class. 

Now, if you were to turn off JavaScript for your local server, you will see that the button is greyed out, and it’s more obvious that the button doesn't do anything. ![Our styling contributes to the understanding that the button is disabled](https://blog.logrocket.com/wp-content/uploads/2022/12/button-disabled-styling.png) 

You could use the same pattern to give your web page any progressive enhancement treatment you want.

## Conclusion

That’s it for this post. We went over web components, their use cases, and how they compare against JavaScript frameworks. 

In addition, we discussed how WebC alleviates some of the drawbacks of web components and helps us reap their full benefits.

---

##[LogRocket](https://lp.logrocket.com/blg/javascript-signup): Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

[LogRocket](https://lp.logrocket.com/blg/javascript-signup) allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

[![LogRocket Signup](https://blog.logrocket.com/wp-content/uploads/2020/06/reproduce-javascript-errors.gif)](https://lp.logrocket.com/blg/javascript-signup)

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

[Try it for free](https://lp.logrocket.com/blg/javascript-signup).