Back to blog
frontendweb-componentsjavascriptui

Building Reusable UI Components with Web Components

Let's talk about Web Components. You've probably spent time building UI components, and likely wrestled with making them portable between projects, or even between different frameworks like React,…

Building Reusable UI Components with Web Components

Let's talk about Web Components. You've probably spent time building UI components, and likely wrestled with making them portable between projects, or even between different frameworks like React, Vue, or Angular. Web Components offer a solution – a standardized way to create reusable UI elements that work *everywhere* modern browsers are supported. They’re not tied to a specific framework, which is a huge win for long-term maintainability and code sharing.

Why Web Components? The Problem They Solve

Think about it. You build a beautiful, functional date picker in React. Then, a new project comes along using Vue. Do you rewrite the date picker? Copy-paste code (shudder)? Or try to shoehorn React into a Vue project? All bad options.

Framework-specific component libraries are great *within* their ecosystems, but they create silos. Web Components break down those silos. They leverage browser APIs to create encapsulated, reusable elements that can be used in any web project, regardless of the framework (or lack thereof!).

This means:

  • True Reusability: Write once, use anywhere.
  • Framework Agnostic: No more framework lock-in.
  • Encapsulation: Styles and behavior are isolated, preventing conflicts.
  • Standardization: Built on web standards, ensuring long-term compatibility.
  • How Web Components Work: The Core Technologies

    Web Components aren't a single technology, but a suite of standards working together. The three main pillars are:

  • Custom Elements: Let you define your own HTML tags.
  • Shadow DOM: Provides encapsulation for your component’s styles and markup.
  • HTML Templates: A mechanism for writing reusable markup fragments.
  • Let's break down each one.

    1. Custom Elements

    This is where you define your new HTML tag. You do this by extending the HTMLElement class.

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

    // Attach a shadow DOM to the element. We'll cover this next. this.attachShadow({ mode: 'open' }); }

    connectedCallback() { // This method is called when the element is added to the DOM. this.shadowRoot.innerHTML = ` <style> p { color: blue; } </style> <p>Hello, world!</p> `; } }

    customElements.define('my-greeting', MyGreeting);

    In this example:

  • We define a class MyGreeting that extends HTMLElement.
  • The constructor initializes the element and attaches a Shadow DOM (more on that in a sec).
  • connectedCallback is a lifecycle method that runs when the element is added to the page. We use it to populate the Shadow DOM with our component's content.
  • customElements.define('my-greeting', MyGreeting) registers our custom element with the browser, associating the tag with our class.
  • Now you can use in your HTML just like any other tag!

    2. Shadow DOM

    The Shadow DOM is *crucial* for encapsulation. It creates a separate, isolated DOM tree attached to your custom element. Styles defined within the Shadow DOM don't leak out and affect the rest of the page, and vice versa. This prevents CSS conflicts and makes your components much more predictable.

    In the example above, this.attachShadow({ mode: 'open' }) creates a Shadow DOM for our MyGreeting element. The mode: 'open' allows JavaScript from the main document to access the Shadow DOM (you can also use mode: 'closed' for stricter encapsulation, but it's less common).

    3. HTML Templates

    Templates let you define reusable HTML fragments that aren't rendered immediately. They're stored in the document and can be cloned and inserted into the DOM as needed. This is useful for complex component structures.

    <template id="my-template">
      <style>
        .highlight { background-color: yellow; }
      </style>
      <p class="highlight">This is a template!</p>
    </template>

    class MyTemplateComponent extends HTMLElement {
      constructor() {
        super();
        this.attachShadow({ mode: 'open' });
      }

    connectedCallback() { const template = document.getElementById('my-template'); const templateContent = template.content.cloneNode(true); this.shadowRoot.appendChild(templateContent); } }

    customElements.define('my-template-component', MyTemplateComponent);

    Here, we fetch the template, clone its content, and append it to the Shadow DOM. This keeps the template definition separate from the component's logic.

    Practical Tips for Building Web Components

  • Lifecycle Callbacks: Understand the different lifecycle callbacks (connectedCallback, disconnectedCallback, attributeChangedCallback, adoptedCallback). They allow you to react to changes in the component's state and environment.
  • Attributes and Properties: Use attributes to configure your component from HTML. Map attributes to properties in your class for easier manipulation in JavaScript. The attributeChangedCallback is key for this.
  • Events: Use CustomEvents to communicate between your component and the outside world. This allows other parts of your application to react to changes within your component.
  • Consider a Base Class: Create a base class for all your Web Components to handle common tasks like attribute handling and event dispatching. This promotes consistency and reduces boilerplate.
  • Styling: While Shadow DOM provides encapsulation, you can still style your components. Use CSS variables (custom properties) to allow external styling. Consider using a CSS-in-JS solution *within* the Shadow DOM for more complex styling needs.
  • Tooling: LitElement and Stencil are popular libraries that simplify Web Component development. They provide features like reactive properties, templating, and build tools.
  • Next Steps & Resources

    Web Components are a powerful tool for building reusable UI. They require a bit of a mindset shift, but the benefits of framework agnosticism and encapsulation are well worth it.

    Here are some resources to get you started:

  • MDN Web Components Documentation: [https://developer.mozilla.org/en-US/docs/Web/Web_Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components)
  • WebComponents.org: [https://webcomponents.org/](https://webcomponents.org/)
  • LitElement: [https://lit.dev/](https://lit.dev/)
  • Stencil: [https://stenciljs.com/](https://stenciljs.com/)
  • Actionable Step: Try building a simple custom element – maybe a button with a custom style. Focus on understanding the core concepts of Custom Elements, Shadow DOM, and lifecycle callbacks. Then, explore one of the libraries like LitElement to see how they can streamline the development process.

    Happy coding! Let us know in the comments if you have any questions or run into any challenges.