Shadow DOM: Una barrera que impide que el CSS se filtre
Al finalizar este tema
Comprenderá qué es el Shadow DOM y por qué es necesaria la encapsulación de CSS, y podrá crear un Shadow DOM para verificar cómo se aíslan los estilos.
El problema de que el CSS externo rompa tus componentes
El equipo ha creado un componente de botón. Fondo azul, texto blanco. Funciona correctamente.
/* Estilos de mi componente */
button { background: blue; color: white; }Sin embargo, otro miembro del equipo añade esto al CSS global:
/* Estilos globales */
button { background: red; color: black; }Mi botón se ha vuelto rojo. Dado que CSS es global por defecto, si existen selectores idénticos, se producen conflictos. Este problema se agrava a medida que el proyecto crece.
Soluciones:
- BEM (
.block__element--modifier) → Evitar conflictos mediante la nomenclatura. Si se rompen las reglas, el diseño se rompe. - CSS Modules → Ofuscar los nombres de las clases mediante hashes en tiempo de compilación. Depende de un framework.
- Shadow DOM → Aislamiento físico a nivel del navegador.
¿Qué es Shadow DOM?
Shadow DOM es una funcionalidad del navegador que crea un pequeño árbol DOM aislado dentro del DOM. El CSS dentro de Shadow DOM no se filtra hacia el exterior, y el CSS externo no penetra en su interior.
// Crear el Shadow DOM
const host = document.getElementById("my-widget");
const shadow = host.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>
button { background: blue; color: white; padding: 12px 24px; }
</style>
<button>Botón del Shadow DOM</button>
`;Este botón no se ve afectado por ningún CSS de la página. Incluso si button { background: red; } se aplica de forma global, este botón seguirá siendo azul.
Ya se estaba utilizando
El slider <input type="range"> del navegador, el botón de reproducción del reproductor <video>: todos ellos son Shadow DOM.
Puedes verlo activando "Show user agent shadow DOM" en Settings de Chrome DevTools:
<input type="range">
#shadow-root (user-agent)
<div id="track">
<div id="thumb"></div>
</div>El navegador utiliza Shadow DOM internamente para encapsular interfaces de usuario complejas. Esta es la razón por la que, aunque apliquemos CSS a la etiqueta input, los elementos internos como el track o el thumb no cambian de forma inesperada.
Web Components — Custom Elements + Shadow DOM
El uso práctico de Shadow DOM destaca en Web Components:
class MyCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
max-width: 300px;
}
h2 { margin: 0 0 8px; color: #333; }
p { margin: 0; color: #666; }
</style>
<div class="card">
<h2><slot name="title">Título</slot></h2>
<p><slot>Contenido</slot></p>
</div>
`;
}
}
customElements.define("my-card", MyCard);<my-card>
<span slot="title">Cultivo celular</span>
El cultivo celular es una técnica fundamental de la experimentación biológica.
</my-card>La etiqueta <my-card> funciona en cualquier framework. React, Vue, Angular, HTML puro: todos son compatibles. La independencia de los frameworks es el valor fundamental de los Web Components.
open vs closed
// open — permite acceder al Shadow DOM desde el exterior
const shadow = el.attachShadow({ mode: "open" });
console.log(el.shadowRoot); // objeto Shadow DOM
// closed — bloquea el acceso desde el exterior
const shadow = el.attachShadow({ mode: "closed" });
console.log(el.shadowRoot); // nullclosed no tiene fines de seguridad, ya que existen métodos para eludirlo. Es más bien una expresión de intención, similar a decir "no tocar el interior". En la mayoría de los casos, se utiliza open.
Métodos de penetración de estilo
El aislamiento completo no siempre es lo ideal. Para aplicar temas (como el modo oscuro, etc.), es necesario un método para inyectar estilos desde el exterior:
// Las propiedades personalizadas de CSS atraviesan el límite del Shadow DOM
shadow.innerHTML = `
<style>
button {
background: var(--theme-primary, blue);
color: var(--theme-text, white);
}
</style>
<button>Botón temático</button>
`;/* Configurar el tema desde el exterior */
:root {
--theme-primary: #6200ea;
--theme-text: #ffffff;
}Las propiedades CSS personalizadas (--nombre-variable) se heredan a través de los límites del Shadow DOM. Este es el punto de extensión previsto: quien crea el componente decide qué partes se pueden personalizar.
Relación con React/Vue
| Shadow DOM | React | Vue (Scoped) | |
|---|---|---|---|
| Nivel de aislamiento | Nivel del navegador (físico) | Tiempo de compilación (lógico) | Tiempo de compilación (atributo) |
| Dependencia del framework | Ninguna (API estándar) | Requiere React | Requiere Vue |
| SSR | Difícil | Compatible | Compatible |
| Ecosistema | Pequeño | Enorme | Enorme |
El CSS-in-JS de React o el <style scoped> de Vue también aíslan los estilos. Sin embargo, al transformar los nombres de las clases en tiempo de compilación, resultan ineficaces ante el CSS inyectado en tiempo de ejecución. El Shadow DOM es el aislamiento real impuesto por el navegador.
En la práctica, también se utiliza Shadow DOM junto con React. Al incrustar un widget en otro sitio web (como un widget de chat, herramientas de análisis, etc.), envolverlo con Shadow DOM bloquea por completo la interferencia del CSS del sitio host.
Idea clave: El Shadow DOM es una función estándar del navegador que crea un subárbol aislado dentro del DOM, evitando que el CSS se filtre. El botón de reproducción de la etiqueta
<video>también es Shadow DOM, y es una tecnología central de los Web Components.