Qué son los CSS Shadow Parts, cómo usar el selector ::part() para conseguir aplicar estilos CSS globales a los custom elements con Shadow DOM.
CSS parts es una nueva tecnología que nos aporta el lenguaje CSS y que viene a solucionar una de las demandas más importantes de los desarrolladores de Web Components de los últimos años.
En este artículo te vamos a explicar cómo trabajar con CSS Part en nuestros custom elements y cómo escribir estilos globales CSS para que penetren en el shadow DOM.
El problema del CSS y el Shadow DOM
Una de las ventajas que nos aporta el desarrollo de Web Components es la encapsulación de los componentes en lo que se conoce como Shadow DOM. Este estándar Javascript permite que los componentes que desarrollamos estén aislados de otras partes de la página y que no sufran interferencias indeseadas.
Realmente puede ser un poco impreciso decir que el shadow DOM deje completamente asislado al componente. Existen algunos estilos que, naturalmente, se aplican a todos los componentes, como por ejemplo el font-family o el color. Si estos estilos no están redefinidos dentro del propio componente, se usarán los definidos en el CSS global. Pero este es el comportamiento habitual del HTML y el CSS, por lo que no debería de extrañarnos.
Esto soluciona diversos problemas habituales que existían anteriormente con otros sistemas como los plugins de jQuery, que cuando tenías varios en la página podrían romperse por interacciones entre ellos, o por el CSS que habíamos definido en la página a nivel global.
Con Shadow DOM los estilos CSS globales, definidos para toda la página, no pueden causar problemas en los componentes, lo que facilita mucho su reutilización. Sin embargo esto también es un arma de doble filo que ha causado conflictos de opiniones entre la comunidad de desarrolladores, especialmente aquellos que están acostumbrados a librerías sin encapsulación, como React, donde sí que se pueden usar los estilos globales dentro de los componentes.
Muchos desarrolladores acostumbrados también a trabajar con frameworks CSS como Bootstrap o Tailwind veían que al usar Web Components no podían reutilizar todas las clases del framework, lo que les resultaba en una experiencia inesperada y frustrante.
Obviamente, hay maneras de conseguir que los CSS globales consigan penetrar en los componentes, como las CSS Custom Properties (variables CSS), sin embargo hasta ahora se quedaban cortas o eran ineficaces.
- Por ejemplo, algunos desarrolladores decidían no usar Shadow DOM, lo que es perfectamente posible, pero con ello perdemos muchas de las ventajas del componente y se hace mucho más difícil trabajar con slots.
- Otros desarrolladores deciden usar @import en las declaraciones de los componentes, trayendo consigo una declaración de CSS global a cada custom element, pero eso es totalmente desaconsejado porque crea cantidades inmensas de código CSS que en realidad se está duplicando y mulplicando por la página, para cada vez que se usa el componente. Por supuesto, esta técnica influiría negativamente en el rendimiento de las aplicaciones.
CSS Parts mejora la aplicación de estilos en componentes con Shadow DOM
El Shadow DOM ha venido para quedarse y sus ventajas superan mucho a sus inconvenientes. Los avances en el estándar CSS y Web Components están dirigidos a permitir que la encapsulación de los Custom Elements sea un problema menor para los desarrolladores.
Aquí es donde CSS Part aparece, con el objetivo de mejorar sensiblemente las posibilidades del CSS global en componentes encapsulados con Shadow DOM. La idea es simplemente proporcionar un mecanismo para que los desarrolladores puedan crear CSS en los estilos globales y que este CSS pueda aplicarse en los componentes.
De este modo, podemos aplicar CSS desde fuera de los componentes con estilos tan complejos como sea necesario, consiguiendo que aumente el grado de personalización de los componentes, sin tener que modificar el código de los propios componentes.
Conociendo el selector CSS ::part()
::part
es un pseudo-elemento de CSS que permite aplicar estilos a otros componentes usados en la página, cuyos elementos pertenecen a un Shadow DOM.
Para que funcione deben entrar en juego dos requisitos:
- En los elementos del Shadow DOM, o sea, en el marcado HTML del template, debemos usar el atributo "part" indicando el nombre de esta parte del componente. Este atributo "part" se puede colocar en cualquier etiqueta y contendrá el valor del nombre de esta CSS part.
- En el CSS global, usaremos el pseudo-elemento ::part, indicando la parte del componente a la que nos estamos refiriendo para aplicar los estilos.
Ahora vamos a ver algunos ejemplos para que quede todo perfectamente claro.
Usando el atributo part en el template de nuestro custom element
Primero vamos a ver cómo se usa el atributo part
, que debemos colocar en las etiquetas HTML sobre las que queremos que desde fuera se les puedan aplicar estilos CSS.
<button part="un_boton">Esto es un botón estilizable desde fuera</button>
Como puedes ver, definir un CSS part es tan sencillo como aplicar un simple atributo HTML. Cada sección del componente que quieras que se pueda estilizar desde fuera necesita que se le asigne este atributo, dando un nombre a esa part, en este caso sería "un_botton
".
Aplicando estilos con el pseudo-elemento ::part
Ahora vamos a ver cómo se le puede aplicar estilos a ese botón. Vamos a suponer que ese botón se haya usado en un custom element llamado "mi-elemento
". Entonces podrías usar este CSS para aplicar estilos al botón que habría dentro del componente <mi-elemento>
.
mi-elemento::part(un_boton) {
border: 1px solid orange;
font-size: 2rem;
}
También podríamos definir el "part" sin indicar el nombre del componente, para que afecte a todos los CSS parts que haya con este mismo nombre.
::part(un_boton) {
border: 1px solid orange;
font-size: 2rem;
}
De este modo, si hay varios componentes en la página que tienen botones con part="un_boton"
, todos se verían afectados por las reglas de estilo anteriores. La ventaja en este caso es que unas mismas reglas de CSS se pueden además especificar en un único lugar, lo que reduce la cantidad de código que debe ser escrito e interpretado por el navegador.
Es interesante mencionar que ::part()
tiene más especificidad que los propios estilos definidos en el componente. Por ello, si los estilos globales definidos con ::part()
colisionan con estilos definidos en el propio componente, ganarán los ::part()
globales. Dicho de otro modo, si colocas un part en un componente lo estás exponiendo a cualquier posible manipulación de su estilo, incluso aunque tú hayas definido el CSS de una manera particular en el custom element. Como alternativa siempre podríamos usar !important
, para conseguir vencer en los estilos definidos globalmente en un CSS Part, si es que fuera necesario para un componente en particular.
Estos selectores pueden complicarse un poco más usando otras pseudo-clases como :hover
o :focus
.
::part(un_boton):hover {
border: 2px solid red;
background-color: #666;
color: #ffc;
}
Compatibilidad de CSS Parts con los navegadores actuales
Esta característica de las CSS y Web Components la podemos usar con total tranquilidad, dado que está actualmente disponible en todos los navegadores actuales.
Por supuesto, Internet Explorer no la admite, pero ya sabemos que este navegador tiene una cuota de mercado residual, y ahora más desde que la propia Microsoft ha dejado de darle soporte.
Así pues gracias a ::part
es posible ahora que desarrolladores reacios a usar Web Components se sientan más atraídos por este estándar Javascript.
Bonus: Cómo podrías usar clases Tailwind en componentes con Shadow DOM
Como hemos dicho, muchas de las reticencias al uso de Web Components provenían de la incapacidad de usar frameworks de CSS global, como Tailwind o Bootstrap. Gracias al selector ::part()
ahora esto tendría una manera de solucionarse más fácilmente.
En el caso de Tailwind la solución más sencilla sería aplicar @apply para definir los estilos globales de un CSS part, utilizando en ella todas las clases de Tailwind que quieras que penetren en el shadow DOM.
::part('navegador') {
@apply p-4 m-2 bg-slate-700 text-white;
}
De esta manera, todos los componentes donde le pongamos el atributo part="navegador"
a uno de sus elementos, tendrán los estilos Tailwind que se están definiendo con el @apply
. Es verdad que seguiríamos sin poder usar directamente las clases de Tailwind en el template del componente, pero sería perfectamente viable usarlas a través del estilo global en las parts donde las queramos aplicar.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...