In the modern frontend landscape, developers are increasingly moving away from framework-specific UI libraries toward standard, interoperable solutions. Enter Web Components: a suite of different technologies allowing you to create reusable custom elements. While frameworks like React, Vue, and Angular have popularized component-based architecture, Web Components run natively in the browser without the need for compilation or bundling. In this guide, we will explore the two core pillars of Web Components—Custom Elements and Shadow DOM—and build a functional, encapsulated component from scratch.
Understanding the Core Concepts
To effectively build Web Components, one must understand the distinction between Custom Elements and the Shadow DOM. Custom Elements are the backbone, allowing you to define your own HTML tags (like <my-button>) and hook into the browser’s element lifecycle. The Shadow DOM provides styling and structural encapsulation, ensuring that your component’s styles and markup do not leak out and are not affected by the rest of the page.
By combining these two, you achieve true modularity. Your component becomes a self-contained unit that can be dropped into any project, regardless of the underlying technology stack.
Step 1: Creating the Custom Element Class
The first step is to create a JavaScript class that extends HTMLElement. This class will define the behavior and lifecycle of your custom element. We will start by creating a simple "Info Card" component. This component will display a title, an icon, and a description.
Here is the basic structure of our class:
class InfoCard extends HTMLElement {
constructor() {
super();
// Attach Shadow DOM here in the next step
}
connectedCallback() {
// This method is called when the component is added to the DOM
this.render();
}
}
The connectedCallback lifecycle method is crucial. It acts as the equivalent of a componentDidMount in React, signaling that the element is now part of the document and ready to be rendered.
Step 2: Implementing Shadow DOM Encapsulation
To ensure our component looks and behaves independently of the parent document, we need to attach a Shadow Root. We do this inside the constructor by calling this.attachShadow(). The mode 'open' allows the shadow root to be accessed via JavaScript from outside the component, which is useful for debugging, though 'closed' offers stricter encapsulation.
class InfoCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
render() {
// Define internal styles and markup
this.shadowRoot.innerHTML = `
System Alert
This is a native Web Component.
`;
}
}
Note the use of CSS scoping. Because these styles are inside the Shadow DOM, they will not affect the global stylesheet, nor will global styles affect the component's internal structure. This solves one of the most persistent problems in web development: style collisions.
Step 3: Registering the Element
Defining the class is not enough; the browser needs to know that <info-card> refers to our custom class. We use customElements.define() to register the component. It is important to note that custom element names must contain a hyphen (e.g., info-card) to avoid conflicts with future native HTML elements.
customElements.define('info-card', InfoCard);
Once registered, you can use the tag <info-card></info-card> anywhere in your HTML body. The component will automatically instantiate and render.
Conclusion
Building Web Components is not just about learning a new API; it is about adopting a philosophy of reusable, standards-compliant code. By leveraging Custom Elements and Shadow DOM, you create robust UI primitives that are framework-agnostic. While this basic example uses innerHTML for simplicity, in production environments, you might consider using template literals or HTML imports for better performance and maintainability.
As the browser standards continue to evolve, Web Components are becoming the backbone of the modern web. Whether you are building a design system, a library, or a single-page application, mastering these fundamentals will make you a more versatile and effective frontend developer.