Web Components: Creando Custom Elements Reutilizables

13 de diciembre de 2025
Osman Jimenez
Web Components JavaScript Desarrollo Web

Web Components Nativos

Los Web Components permiten crear elementos HTML personalizados y reutilizables sin frameworks. Son el estándar web para componentes encapsulados.

Anatomía de un Web Component

class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  
  connectedCallback() {
    this.render();
    this.addEventListener('click', this.handleClick);
  }
  
  disconnectedCallback() {
    this.removeEventListener('click', this.handleClick);
  }
  
  render() {
    this.shadowRoot.innerHTML = `
      
      
    `;
  }
  
  handleClick = () => {
    this.dispatchEvent(new CustomEvent('my-click', {
      detail: { timestamp: Date.now() },
      bubbles: true,
      composed: true
    }));
  }
}

customElements.define('my-button', MyButton);

Uso del Componente

<my-button>Click Me</my-button>

<script>
  document.querySelector('my-button')
    .addEventListener('my-click', (e) => {
      console.log('Clicked at:', e.detail.timestamp);
    });
</script>

Atributos y Propiedades

class UserCard extends HTMLElement {
  static get observedAttributes() {
    return ['name', 'email', 'avatar'];
  }
  
  get name() {
    return this.getAttribute('name');
  }
  
  set name(value) {
    this.setAttribute('name', value);
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      this.render();
    }
  }
  
  render() {
    this.shadowRoot.innerHTML = `
      
      

${this.getAttribute('name')}

${this.getAttribute('email')}

`; } } customElements.define('user-card', UserCard);

Slots para Contenido Flexible

class CardComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      
      
Default Header
`; } } customElements.define('card-component', CardComponent);

Uso con Slots

<card-component>
  <span slot="header">Mi Título</span>
  <p>Contenido principal de la tarjeta</p>
  <button slot="footer">Acción</button>
</card-component>

Templates y Shadow DOM

<template id="product-card-template">
  
  

</template> <script> class ProductCard extends HTMLElement { constructor() { super(); const template = document.getElementById('product-card-template'); const content = template.content.cloneNode(true); this.attachShadow({ mode: 'open' }).appendChild(content); } connectedCallback() { this.shadowRoot.querySelector('img').src = this.getAttribute('image'); this.shadowRoot.querySelector('.name').textContent = this.getAttribute('name'); this.shadowRoot.querySelector('.price').textContent = this.getAttribute('price'); } } customElements.define('product-card', ProductCard); </script>

Lifecycle Callbacks

class LifecycleComponent extends HTMLElement {
  constructor() {
    super();
    console.log('Constructor: Componente creado');
  }
  
  connectedCallback() {
    console.log('Connected: Agregado al DOM');
  }
  
  disconnectedCallback() {
    console.log('Disconnected: Removido del DOM');
  }
  
  adoptedCallback() {
    console.log('Adopted: Movido a nuevo documento');
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    console.log(`Attribute ${name} changed: ${oldValue} -> ${newValue}`);
  }
}

Integración con Frameworks

En React

import { useRef, useEffect } from 'react';

function App() {
  const buttonRef = useRef();
  
  useEffect(() => {
    const button = buttonRef.current;
    const handler = (e) => console.log(e.detail);
    button.addEventListener('my-click', handler);
    return () => button.removeEventListener('my-click', handler);
  }, []);
  
  return Click Me;
}

En Angular

// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})

// component.html
Click Me

Mejores Prácticas

  1. Usa Shadow DOM: Para encapsulación de estilos
  2. Nombres con guión: Requerido por el estándar (my-component)
  3. Cleanup en disconnectedCallback: Evita memory leaks
  4. Eventos personalizados: Usa composed: true para atravesar Shadow DOM
  5. Accesibilidad: Incluye ARIA attributes

Conclusión

Los Web Components son el futuro de los componentes web reutilizables. Son framework-agnostic, tienen soporte nativo del navegador y permiten crear bibliotecas de componentes verdaderamente portables.