The web Components v1 specification consists of three main technologies that can be used to create reusable custom elements:
- Custom elements
- HTML templates
- Shadow DOM
Custom elements
Custom elements allow you to define new tags by declaring classes that extend the HTMLElement
base class.
class MyButton extends HTMLElement {
// (...)
}
The HTMLElement
base class exposes callback functions you can use to do stuff at any point in the component lifecycle.
class MyButton extends HTMLElement {
attributeChangedCallback(name, oldValue, newValue) {
// (...)
}
}
With custom elements, you define the component API by exposing attributes and events.
<my-button variant="primary">Click me</my-button>
Iteration 1: The simplest custom element
Let's create the simplest custom element ever so we understand the basics.
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<script src="my-button.js"></script>
</head>
<body>
<my-button></my-button>
</body>
</html>
// my-button.js
class MyWebComponent extends HTMLElement {
connectedCallback() {
this.innerHTML = `<button>hola</button>`;
}
}
customElements.define('my-button', MyWebComponent);
A couple of key parts in this example:
- The
MyWebComponent
class uses theconnectedCallback
life cycle method to add the<button>
markup. Note that we are usingthis.innerHTML = ...
, that is because we are inside theHTMLElement
! We have access to all DOM APIs, which will be scoped to the element. - The
window.customElements
gives you access to the custom elements registry, and thedefine()
method registers the declared class with the provided tag name (which, by the way, must have a dash in its name to ensure there are no collisions with built-in HTML tags).
In this contrived example, we only focused on creating the custom element, which leaves a lot to be desired! Let's spice things up a bit!
Iteration 2: Declarative component API with attributes
Let's expose the button label
as an attribute and allow setting the button look and feel based on the variant
attribute value.
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<script src="my-button.js"></script>
<style>
body {
display: flex;
flex-direction: column;
gap: 8px;
}
button {
cursor: pointer;
font-size: 20px;
font-weight: 700;
padding: 12px;
min-width: 180px;
border-radius: 12px;
}
button.primary {
background-color: #0b66fa;
color: #fff;
border: 0;
}
button.secondary {
border: 1px solid rgba(0, 0, 0, 0.12);
background-color: #fff;
color: #000000de;
}
</style>
</head>
<body>
<my-button label="Default"></my-button>
<my-button variant="primary" label="Primary"></my-button>
<my-button variant="secondary" label="Secondary"></my-button>
<button class="primary">Plain HTML <button></button>
</body>
</html>
// my-button.js
class MyWebComponent extends HTMLElement {
static get observedAttributes() {
return ['variant', 'label'];
}
connectedCallback() {
this.render();
}
attributeChangedCallback() {
this.render();
}
render() {
const label = this.getAttribute('label') || '';
const variant = this.getAttribute('variant') || '';
this.innerHTML = `
<button class="${variant}">${label}</button>
`;
}
}
customElements.define('my-button', MyWebComponent);
A couple of interesting facts about this example:
- If we want the component to be notified of changes to its properties via the
attributeChangedCallback()
method, we have to declare them using theobservedAttributes()
static method. - We've defined a few global CSS styles, and as we can see, all buttons are being styled, whether inside a custom element or not. That's not what we want for our web components. But fear not! Keep reading and find out how to solve this issue.
Coming up next
In the next article we'll isolate our component from the rest of the page while introducing the Shadow DOM, templates and slots.
Top comments (1)
so far so good, thanks for this