Nombres de CSS definidos por el autor y shadow DOM: En especificación y en la práctica

Se supone que los nombres de CSS definidos por el autor y el DOM de sombra funcionan juntos. Sin embargo, los navegadores no son coherentes con la especificación, a veces entre sí, y cada nombre de CSS es incoherente de una manera ligeramente diferente.

En este artículo, se documenta el estado actual del comportamiento de los nombres de CSS definidos por el autor en los alcances de sombras, con la esperanza de que pueda servir como guía para mejorar la interoperabilidad en un futuro cercano.

¿Qué son los nombres de CSS definidos por el autor?

Los nombres de CSS definidos por el autor son un mecanismo de sintaxis de CSS relativamente antiguo, que se introdujo originalmente para la regla @keyframes, que define un <keyframe-name> como una identificación personalizada o una cadena. El propósito de este concepto es declarar algo en una parte de una hoja de estilo y hacer referencia a ella en otra parte.

/* "fade-in" is a CSS name, representing a set of keyframes */
@keyframes fade-in {
  from { opacity: 0 };
  to { opacity: 1 }
}

.card {
  /* "fade-in" is a reference to the above keyframes */
  animation-name: fade-in;
}

Otras funciones de CSS que usan nombres de CSS son las fuentes, las declaraciones de propiedades, las consultas de contenedor y, más recientemente, las transiciones de vista, el posicionamiento de anclaje y las animaciones impulsadas por el desplazamiento. En la siguiente tabla no exhaustiva, se incluyen los nombres cuyos estados Chrome verifica.

Función Declaración de nombre Referencia de nombre
Fotogramas clave @keyframes animation-name
Fuentes @font-face { }
@font-palette-values
font-family
font-palette
Declaraciones de propiedades @property
Cualquier declaración de propiedad personalizada no registrada
var()
Cómo ver transiciones view-transition-name
view-transition-class
Pseudoelementos ::view-transition-*
Posicionamiento de la ancla anchor-name position-anchor
Animación impulsada por el desplazamiento view-timeline-name
scroll-timeline-name
animation-timeline
Estilos de lista @counter-style list-style
Contadores counter-reset
counter-set
counter-increment
Consultas de contenedores container-name @container
Página page @page

Como puedes ver en la tabla, un nombre de CSS suele tener una referencia de CSS correspondiente. Por ejemplo, animation-name es una referencia al nombre @keyframes. Los nombres de CSS son diferentes de los nombres definidos en el DOM, como los atributos y los nombres de etiquetas, ya que se declaran y, luego, se hace referencia a ellos en el contexto de los diseños de página.

Cómo se relacionan los nombres con el shadow DOM

Si bien los nombres de CSS se compilan para crear relaciones entre diferentes partes de un documento o una hoja de estilo, Shadow DOM se compila para hacer lo contrario. Encapsula las relaciones para que no se filtren en los componentes web que deberían tener su propio espacio de nombres.

Cuando se combinan los nombres de CSS y el DOM sombreado, la experiencia de composición de componentes web debería ser lo suficientemente expresiva como para ser flexible, pero lo suficientemente restringida como para ser estable.

Esto es bueno en teoría. En la práctica, los navegadores son incoherentes en la forma en que los nombres de CSS interoperan con el shadow DOM, tanto entre las funciones del mismo navegador, entre navegadores y entre las funciones y la especificación.

Cómo deben funcionar juntos los nombres y el DOM sombreado

Para comprender el problema, vale la pena comprender cómo deberían funcionar juntas estas partes del CSS en teoría.

La regla general

La regla general sobre cómo se comportan los nombres de CSS en los árboles de sombras se define en la especificación de nivel 1 de CSS Scoping. En resumen, un nombre de CSS es global dentro del alcance en el que se define, lo que significa que se puede acceder a él desde árboles de sombras descendientes, pero no desde árboles de sombras hermanos o ancestros. Ten en cuenta que esto es diferente de los nombres en la plataforma web, como los IDs de elementos, que se encapsulan dentro del mismo alcance del árbol.

Excepción a la regla: @property

A diferencia de otros nombres de CSS, las propiedades de CSS no están encapsuladas por Shadow DOM. En cambio, son el medio común para pasar parámetros a través de diferentes árboles de sombras. Esto hace que el descriptor @property sea especial: se supone que se comporta como una declaración de tipo global del documento que define cómo actúa una propiedad nombrada en particular. Debido a que las propiedades deben coincidir en todos los árboles de sombras, la falta de coincidencia de las declaraciones de propiedades crearía resultados inesperados, por lo que se especifica que las declaraciones de @property se aplanen y resuelvan según el orden del documento.

Cómo debería funcionar la regla con ::part

Las partes de sombra exponen un elemento dentro de un árbol de sombras a su árbol superior. De esta manera, el árbol superior puede acceder a ese elemento y también aplicarle un diseño con el elemento ::part.

Dado que ::part permite que dos alcances de árbol apliquen diseño al mismo elemento, se especifica el siguiente orden en cascada:

  1. Primero, verifica el estilo dentro del contexto de sombra. Este es el estilo “predeterminado” de la pieza.
  2. Luego, aplica el estilo externo como se define en ::part. Este es el estilo “personalizado” de la pieza.
  3. Luego, aplica cualquier estilo interno que se defina junto con !important. Esto permite que un elemento personalizado declare que ::part no puede personalizar una propiedad determinada de una parte determinada.

Esto significa que no se puede hacer referencia a los nombres dentro del shadow DOM desde un ::part, ya que ::part es un estilo con el alcance del host en lugar de un estilo con el alcance de la sombra. Por ejemplo:

// inside the shadow DOM:
@keyframes fade-in {
  from { opacity: 0}
}

// This shouldn't work!
// The host style shouldn't know the name "fade-in"
::part(slider) {
  animation-name: fade-in;  
}

Cómo debería funcionar la regla con los estilos intercalados

A diferencia de ::part, los estilos intercalados con el atributo style, o aquellos que configuran el estilo de forma programática con una secuencia de comandos, se aplican al elemento al que se aplica el estilo. Esto se debe a que, para aplicar un estilo a un elemento, necesitas acceso al control del elemento y, por lo tanto, a la raíz de sombra.

Cómo funcionan juntos los nombres de CSS y el DOM sombreado en la realidad

Si bien las reglas anteriores están bien definidas y son coherentes, las implementaciones actuales no siempre lo reflejan. En la práctica, @property funciona de manera diferente a la especificación de una manera coherente en todos los navegadores, y la mayoría de las otras funciones tienen errores abiertos (algunos de los cuales aún no se lanzaron, por lo que hay tiempo para corregirlos).

Para probar y demostrar cómo funcionan estas funciones en la práctica, creamos la siguiente página: https://css-names-in-the-shadow.glitch.me/. Esta página tiene varios iframes, cada uno enfocado en una de las funciones y que prueba seis situaciones:

  • Referencia externa a un nombre externo: No hay shadow DOM involucrado, esto debería funcionar.
  • Referencia externa a un nombre interno: Esto no debería funcionar, ya que eso significaría que se filtró el nombre definido en el contexto de la sombra.
  • Referencia interna al nombre externo: Esto debería funcionar, ya que las raíces de sombra heredan los nombres centrados en el árbol.
  • Referencia interna al nombre interno: Esto debería funcionar, ya que el nombre de la referencia está en el mismo alcance.
  • ::part hace referencia al nombre externo: Esto debería funcionar, ya que ::part y el nombre se declaran en el mismo alcance.
  • Referencia de ::part al nombre interno: Esto no debería funcionar, ya que el alcance externo no debería obtener información sobre los nombres declarados dentro del DOM sombreado.

@keyframes

Como se define en la especificación, deberías poder hacer referencia a los nombres de fotogramas clave desde una raíz de sombra, siempre y cuando la regla de at @keyframes esté en un alcance ancestral. En la práctica, ningún navegador implementa este comportamiento, y solo se puede hacer referencia a las definiciones de fotogramas clave en el alcance en el que se definen. Consulta el problema 10540.

@property

Como se define en la especificación, cualquier declaración de @property se aplanará al alcance del documento. Sin embargo, en la actualidad, en todos los navegadores, solo puedes declarar @property en el alcance del documento, y se ignoran las declaraciones de @property dentro de las raíces de sombra.
Consulta el problema 10541.

Errores específicos del navegador

Las otras funciones no muestran un comportamiento coherente en todos los navegadores:

  • @font-face se aplana al alcance raíz en Safari.
  • Chromium no permite heredar reglas anchor-name en un elemento raíz en sombra.
  • scroll-timeline-name y view-timeline-name no tienen el alcance correcto en ::part (también en Chromium).
  • Ningún navegador permite declarar @font-palette-values en una raíz de sombra.
  • view-transition-class se puede definir dentro de una raíz de sombra (la transición en sí está fuera de la raíz de sombra).
  • Firefox permite que ::part acceda a los nombres de sombras internas (consultas de contenedores, fotogramas clave).
  • Firefox y Safari no respetan @counter-style en un elemento raíz en sombra.

Ten en cuenta que counter-reset, counter-set y counter-increment tienen reglas ligeramente diferentes porque son nombres implícitos, y declarar propiedades CSS tiene un conjunto de reglas establecido y bien probado.

Conclusión

La mala noticia es que, cuando se examina el resumen del estado de interoperabilidad actual con respecto a los nombres de CSS y el DOM sombreado, la experiencia es incoherente y con errores. Ninguna de las funciones que examinamos aquí se comporta de manera coherente en todos los navegadores y según las especificaciones. La buena noticia es que la diferencia para que la experiencia sea coherente es una lista finita de errores y problemas de especificación. Solucionemos esto. Mientras tanto, esperamos que esta sinopsis te ayude si tienes problemas con las inconsistencias que se describen en este artículo.