</> HTML5Advent
ENFRESDEITPT

// html · Web Platform Advent #5

Web Components: Custom Elements, Shadow DOM y plantillas

Crea elementos HTML reutilizables con las API nativas de Web Components — define un Custom Element, encapsula estilos con el Shadow DOM, clona un <template> y pasa datos por atributos.

Un montón de bloques de construcción de plástico de colores y encajables de distintas formas

Los Web Components son un conjunto de API nativas del navegador que te permiten crear tus propios elementos HTML reutilizables — sin ningún framework. Un componente que publicas funciona igual en HTML puro, React, Vue o Svelte, porque es simplemente un elemento que el navegador entiende. Tres API lo hacen posible: los Custom Elements, el Shadow DOM y la etiqueta <template>.

Custom Elements

Un Custom Element es una clase que extiende HTMLElement, registrada con un nombre de etiqueta que debe contener un guion (para no colisionar nunca con una futura etiqueta nativa):

class GreetingCard extends HTMLElement {
  connectedCallback() {
    const p = document.createElement('p');
    p.textContent = '¡Hola desde un custom element!';
    this.append(p);
  }
}

customElements.define('greeting-card', GreetingCard);

Una vez definido, lo usas como cualquier otra etiqueta:

<greeting-card></greeting-card>

La clase te ofrece callbacks de ciclo de vida. Los más útiles son connectedCallback (el elemento se añadió a la página), disconnectedCallback (se eliminó) y attributeChangedCallback (un atributo observado cambió).

Reaccionar a los atributos

Para pasar datos, lee atributos — igual que src en una imagen. Declara cuáles observar con un getter estático observedAttributes y luego responde en attributeChangedCallback. Construir el nodo con textContent mantiene seguros los valores que aporta el usuario:

class GreetingCard extends HTMLElement {
  static get observedAttributes() {
    return ['name'];
  }
  attributeChangedCallback(attr, oldVal, newVal) {
    if (attr === 'name') this.render();
  }
  connectedCallback() {
    this.render();
  }
  render() {
    const name = this.getAttribute('name') || 'amigo';
    this.replaceChildren();
    const p = document.createElement('p');
    p.textContent = '¡Hola, ' + name + '!';
    this.append(p);
  }
}
customElements.define('greeting-card', GreetingCard);

Ahora <greeting-card name="Ada"></greeting-card> muestra «¡Hola, Ada!», y cambiar el atributo en vivo lo vuelve a renderizar.

Una caja de herramientas metálica abierta con herramientas ordenadas en compartimentos separados
Un web component es como una herramienta en una caja: una unidad autónoma y etiquetada que tomas y reutilizas en muchos proyectos sin reconstruirla cada vez.

El Shadow DOM: encapsulación de verdad

El Shadow DOM le da a un elemento un subárbol privado cuyos estilos y marcado están aislados del resto de la página. Un <style> dentro nunca puede filtrarse, y el CSS exterior nunca puede entrar por accidente. Constrúyelo con la API del DOM y un <slot> para el contenido proyectado:

class FancyButton extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'open' });

    const style = document.createElement('style');
    style.textContent =
      'button { background:#2563eb; color:#fff; border:0;' +
      ' border-radius:8px; padding:.6rem 1rem; }';

    const button = document.createElement('button');
    button.append(document.createElement('slot'));

    shadow.append(style, button);
  }
}
customElements.define('fancy-button', FancyButton);

El <slot> es un marcador de posición: lo que pongas entre las etiquetas — <fancy-button>Guardar</fancy-button> — se proyecta en ese punto, así que el componente envuelve tu contenido sin reemplazarlo.

Plantillas: clonar una vez, reutilizar barato

El elemento <template> contiene marcado inerte, analizado pero no renderizado hasta que lo clonas. Es la forma eficiente de repetir una estructura:

<template id="row-tpl">
  <li><span class="label"></span></li>
</template>

<script>
  const tpl = document.getElementById('row-tpl');
  const node = tpl.content.cloneNode(true);
  node.querySelector('.label').textContent = 'Elemento 1';
  document.querySelector('ul').append(node);
</script>

Las tres piezas juntas

APILo que aporta
Custom ElementsTus propias etiquetas con callbacks de ciclo de vida
Shadow DOMEncapsulación de estilo y marcado, slots
<template>Marcado inerte reutilizable y clonable

Rara vez necesitas las tres a la vez. Un simple contenedor puede usar solo un Custom Element; un widget con estilo y autónomo combina un Custom Element con el Shadow DOM y un slot. Como el resultado es DOM estándar, encaja en cualquier stack y sobrevive al framework que estuviera de moda el año en que lo escribiste.