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
- Usa Shadow DOM: Para encapsulación de estilos
- Nombres con guión: Requerido por el estándar (my-component)
- Cleanup en disconnectedCallback: Evita memory leaks
- Eventos personalizados: Usa composed: true para atravesar Shadow DOM
- 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.