La eterna promesa de los Web Components
Cada año alguien declara que “este es el año de los Web Components”. Y cada año, la realidad es más matizada. Pero en 2026, la situación ha cambiado de verdad. No porque los Web Components hayan reemplazado a React (no lo han hecho), sino porque finalmente resuelven un problema específico mejor que cualquier alternativa.
He usado Web Components en 3 proyectos en los últimos 2 años. Un design system compartido entre React y Vue, un widget de chat embebido, y un set de componentes de UI para un proyecto con Astro. Mi veredicto: son excelentes para lo que están diseñados y frustrantes cuando intentas usarlos para todo.
Qué ha cambiado en 2025-2026
Declarative Shadow DOM
El problema histórico más grande de los Web Components era el SSR (Server-Side Rendering). El Shadow DOM solo se podía crear con JavaScript, lo que significaba que el contenido no existía hasta que el JS se ejecutaba en el cliente. Esto era terrible para SEO y rendimiento.
Declarative Shadow DOM resuelve esto. Ahora puedes declarar el Shadow DOM en HTML puro:
<my-card>
<template shadowrootmode="open">
<style>
:host { display: block; border: 2.5px solid #1a1a1a; border-radius: 20px; padding: 1.5rem; }
::slotted(h2) { font-weight: 700; margin-bottom: 0.5rem; }
</style>
<slot name="title"></slot>
<slot></slot>
</template>
<h2 slot="title">Mi tarjeta</h2>
<p>Contenido visible sin JavaScript.</p>
</my-card>
El servidor puede renderizar esto como HTML puro. El navegador lo interpreta como Shadow DOM sin necesitar JavaScript. Esto cambia completamente la ecuación para SSR.
ElementInternals y Form-Associated Custom Elements
Los Web Components ahora pueden participar en formularios nativos usando ElementInternals:
class MyInput extends HTMLElement {
static formAssociated = true;
constructor() {
super();
this.internals = this.attachInternals();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<label><slot></slot></label>
<input type="text" />
`;
this.shadowRoot.querySelector('input').addEventListener('input', (e) => {
this.internals.setFormValue(e.target.value);
});
}
}
customElements.define('my-input', MyInput);
Funciona, pero es verbose comparado con un <input> con React Hook Form. La API de ElementInternals es potente pero no ergonómica.
CSS Custom State y :state()
Los Web Components ahora exponen estados custom que se pueden estilar desde fuera:
/* Estilizar un componente desde fuera basándose en su estado interno */
my-button:state(loading) {
opacity: 0.5;
pointer-events: none;
}
Esto resuelve un dolor de cabeza histórico: cómo estilar componentes encapsulados sin romper la encapsulación.
Cuándo usar Web Components (y cuándo no)
El caso perfecto: Design Systems multi-framework
Si tu empresa usa React en el producto principal, Vue en el backoffice y Astro en el marketing site, mantener 3 implementaciones del mismo botón es una pesadilla. Web Components resuelven esto: un componente, todas las plataformas.
Empresas como Adobe (Spectrum), GitHub (Primer), y Salesforce (Lightning) usan Web Components exactamente para esto. Un <my-button> funciona en React, Vue, Svelte, Angular, Astro, y vanilla HTML sin adapters.
Buen caso: Widgets embebibles
Si construyes un widget que otros sitios van a incrustar (un chat, un formulario, un reproductor), los Web Components son ideales. El Shadow DOM aísla tus estilos del sitio host, evitando conflictos de CSS.
Mal caso: Aplicaciones completas
Construir una app entera con Web Components vanilla es posible pero doloroso. No tienes routing, state management, o templating reactivo out-of-the-box. Lit ayuda, pero sigue siendo más trabajo que React o Vue para apps con lógica compleja.
Mal caso: Proyectos con un solo framework
Si todo tu stack es React, no necesitas Web Components. Los componentes React son más ergonómicos, tienen mejor tooling, y no añaden la complejidad del Shadow DOM. Usa Web Components solo cuando necesitas interoperabilidad.
Lit: el framework que hace viables los Web Components
Lit es la librería de Google que hace que escribir Web Components sea tolerable. Añade templating reactivo, decoradores de TypeScript y un sistema de propiedades/atributos que simplifica mucho el boilerplate.
Un componente con Lit:
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('brutal-card')
export class BrutalCard extends LitElement {
static styles = css`
:host {
display: block;
border: 2.5px solid var(--color-dark, #1a1a1a);
border-radius: 20px;
padding: 1.5rem;
box-shadow: 4px 4px 0 var(--color-dark, #1a1a1a);
}
:host(:hover) {
transform: translateY(-4px) rotate(-0.5deg);
box-shadow: 6px 6px 0 var(--color-dark, #1a1a1a);
}
`;
@property() title = '';
@property() description = '';
render() {
return html`
<h2>${this.title}</h2>
<p>${this.description}</p>
<slot></slot>
`;
}
}
Lit compila a ~5 KB (gzip) y produce Web Components estándar que funcionan en cualquier contexto. Es mi recomendación si vas a trabajar con Web Components.
Integración con Astro
Astro tiene soporte nativo para Web Components. Puedes usarlos como cualquier elemento HTML:
---
// No necesitas import — son custom elements
---
<brutal-card title="Mi título" description="Mi descripción">
<span slot="footer">Más info →</span>
</brutal-card>
<script>
// Registra el componente en el cliente
import '../components/brutal-card.js';
</script>
Con Declarative Shadow DOM, el contenido es visible incluso antes de que el JavaScript cargue. La hidratación es transparente.
Mi veredicto para 2026
Los Web Components están listos para producción en su nicho: design systems compartidos, widgets embebibles, y componentes que necesitan funcionar en múltiples frameworks.
No están listos para reemplazar React, Vue o Svelte como framework de aplicaciones. Y posiblemente nunca lo estén — no es su propósito.
Si tienes un caso de uso claro para interoperabilidad, úsalos. Si no, quédate con tu framework favorito. No añadas complejidad sin necesidad.