El Shadow DOM es un límite de estilo: las reglas de la página no llegan al interior y las reglas de los componentes no se filtran hacia el exterior, excepto las propiedades heredadas (incluidas las propiedades personalizadas). Cruza la línea de forma deliberada a través de los siguientes canales.
:host, :host(), :host-context()
/* The component's own host element, styled from inside its shadow tree. */
:host { display: block; }
/* Only when the host matches a selector (e.g. a reflected state attribute). */
:host([disabled]) { opacity: 0.5; pointer-events: none; }
/* Based on an ancestor in the Light DOM; useful for theming. */
:host-context([theme="dark"]) { background: #111; color: #eee; }
Evita :host-context(). Llega fuera del límite para probar los ancestros, pero Firefox y Safari se negaron a implementarlo y el CSSWG se inclina por no seguir desarrollándolo. Trátalo como un callejón sin salida exclusivo de Chromium, no como una opción con visión de futuro. En su lugar, aplica el tema a través de una propiedad personalizada de CSS heredada (a continuación): el consumidor establece la propiedad en un elemento superior y el componente la lee más allá del límite, lo que funciona en todas partes y mantiene ambos lados separados.
Las propiedades heredadas y las propiedades personalizadas cruzan el límite
Las propiedades de CSS heredadas (color, font-family, line-height y las propiedades personalizadas de CSS) sí pasan por el límite de sombra. Esto hace que las propiedades personalizadas sean la API de temas principal: exponlas con resguardos razonables.
:host {
/* Consumers override --card-bg from outside; the fallback keeps it usable. */
background: var(--card-bg, white);
color: var(--card-fg, black);
}
Una propiedad personalizada definida en :host solo se puede anular con reglas que se orienten específicamente al elemento host. Para que herede y se pueda anular desde cualquier lugar del árbol, deja la propiedad pública sin establecer en :host y confía en el resguardo de cada var(); no propagues la propiedad pública en :host.
Para mantener el resguardo declarado una vez en lugar de repetirlo en cada var(), define una propiedad "shadow" privada en :host que lea la pública y haz referencia a la propiedad privada de forma interna:
:host {
--_card-bg: var(--card-bg, white);
--_card-fg: var(--card-fg, black);
}
.card { background: var(--_card-bg); color: var(--_card-fg); }
Los consumidores aún anulan --card-bg desde el exterior; el guion bajo inicial indica que el alias es interno y el resguardo se encuentra en un solo lugar.
::slotted(): Aplicar diseño al Light DOM proyectado
::slotted(selector) se orienta a los nodos de nivel superior asignados a un espacio. No puede llegar a sus descendientes y pierde ante cualquier regla del DOM de luz (el consumidor es propietario de su propio contenido), por lo que debes usarlo para retoques ligeros, no para controlar. ::slotted(...) !important puede anular las reglas del consumidor, pero, si lo hace, anula el estilo que el consumidor tiene derecho a poseer. Resérvalo para correcciones estructurales, no para anulaciones estéticas.
/* Matches a slotted <p>, but NOT a <p> nested inside a slotted <div>. */
::slotted(p) { margin: 0; }
::part() y exportparts: Exponen elementos internos para la aplicación de temas.
Marca un elemento interno con part="name" para permitir que los consumidores le den estilo con cualquier propiedad, sin necesidad de conocer tu estructura interna.
<!-- inside the shadow template -->
<button part="submit">Submit</button>
/* consumer stylesheet */
my-card::part(submit) { background: navy; color: white; }
Expón algunas partes de alto nivel (botón, entrada, etiqueta), no todas las <div>; las superficies de partes amplias se convierten en una API frágil. Para volver a exponer las partes de un componente anidado hacia arriba a través de tu propio componente, reenvíalas con exportparts:
<inner-widget exportparts="submit: card-submit"></inner-widget>
Elige entre los dos canales de forma deliberada: propiedades personalizadas para un conjunto de valores restringido y seguro para el sistema de diseño; partes para aplicar un estilo sin restricciones a un elemento específico.
Cómo reenviar estilos a un elemento nativo unido
Cuando un elemento personalizado une un control nativo (<my-button> alrededor de un <button> real), las personas suelen cometer tres errores:
- Los controles de formulario no heredan la tipografía.
colory las propiedades personalizadas cruzan el límite, pero<button>,<input>y<select>tomanfonty otros elementos de la hoja de estilo de UA, que no se hereda. Por lo tanto, el control envuelto se renderiza en la fuente de la plataforma, no en la fuente de la página, hasta que establezcasfont: inherit(y, por lo general,color: inherit) en él dentro del árbol de sombra. Este es el error más común. - Los estilos de
:hostno llegan al elemento interno. Si configurasbackground/border/paddingen:host, se aplica el estilo al cuadro de host, no al control que contiene. Reenvía de forma deliberada: Expón el control como un::partpara un diseño abierto o canaliza un conjunto fijo de valores a través de propiedades personalizadas; una regla:hostno se aplica en cascada al elemento unido. display: contents+all: inherites una compensación real, no un atajo. Contraer el host condisplay: contentsy restablecer el control interno conall: inherites, actualmente, la única forma genérica de pasar estilos arbitrarios a un elemento nativo unido sin que los consumidores aprendan partes por componente o propiedades personalizadas, y quitar la propia caja del host suele ser el objetivo del patrón (la composición compensa una herencia integrada faltante). Dos advertencias reales:all: inheritborra los valores predeterminados de UA, por lo que debes definir el alcance de propiedades específicas o usarrevertcuando quieras recuperar los valores predeterminados; y el bloqueador concreto:getBoundingClientRect()ygetClientRects()devuelven cero para los elementosdisplay: contents, por lo que el posicionamiento de anclaje contra el host está roto. Usa::parto propiedades personalizadas cuando esas compensaciones no sean adecuadas.
:state(): Aplica diseño a los estados del componente
Un elemento personalizado puede exponer estados booleanos internos a través de ElementInternals con this.#internals.states.add('busy') y .delete('busy') (consulta Elementos personalizados para conocer el uso del ciclo de vida), y esos estados se convierten en un canal de diseño que cruza el límite, como ::part y las propiedades personalizadas, pero para el estado en lugar de la estructura o los valores. Haz que coincidan dentro del árbol de sombra con :host(:state(name)) y desde la hoja de estilo de un consumidor con tag-name:state(name):
/* Inside the component's shadow tree. */
:host(:state(busy)) button { opacity: 0.6; pointer-events: none; }
/* From the consumer's stylesheet — a read-only public hook. */
favorite-button:state(favorited) { filter: drop-shadow(0 0 4px crimson); }
A diferencia de un atributo reflejado, un estado personalizado es de solo lectura para el exterior: los consumidores pueden aplicarle diseño, pero no pueden establecerlo ni falsificarlo, por lo que el componente sigue siendo el único escritor. Se prefiere por sobre un atributo data-* siempre que el estado sea propiedad del componente y solo se deba aplicar un estilo.
@layer y consultas de contenedores dentro de los componentes
- Las capas en cascada (
@layer) funcionan dentro de una raíz de sombra y se limitan a ella; el nombre de una capa en el árbol de sombra es independiente de una capa con el mismo nombre en la página. Úsalas para ordenar tus propios estilos de componentes (p.ej.,@layer base, theme;) de modo que las anulaciones de los consumidores a través de propiedades personalizadas y::partsigan ganando de forma predecible. - Las consultas de contenedores son la herramienta adecuada para los componentes responsivos: un componente debe responder al espacio que se le asigna, no al viewport. Declara el host o un wrapper como un contenedor (proporciona a
:hostun cuadro de diseño, comodisplay: block, ya que los elementos personalizados se establecen de forma predeterminada endisplay: inliney la contención de tamaño no se aplica a los elementosinlineocontents) y consúltalo para que el mismo componente se adapte en una barra lateral o en una región de ancho completo.
:host {
display: block; /* Required: size containment does not apply to default display: inline */
container-type: inline-size;
}
@container (min-width: 30rem) {
.card { grid-template-columns: 1fr 2fr; }
}
Estrategias de resguardo
::part, las capas en cascada y las consultas de contenedores están ampliamente disponibles. El tema se basa principalmente en las propiedades personalizadas heredadas, que funcionan incluso cuando estas no lo hacen. Como se indicó anteriormente, no confíes en :host-context(), ya que solo es para Chromium y no es una opción con visión de futuro.