Hace unos días tuve un problema con CSS (en realidad eso es todos los días), desarrollando unas features me di cuenta de algo realmente feo que estaba pasando.
El problema
¡¡¡Mis estilos estaban siendo sobre-escritos!!!. Esto es un problema común cuando no se tiene una metodología o un estándar para hacer CSS. Estos problemas son normalmente por no tener en cuenta como la ubicación del CSS, la cascada y la especifidad afectan a tus elementos.
En mi caso la raíz de todo fue la cascada y no la especificidad ni el origen. La cascada decide que estilo se aplica y cual no, cuando estos tiene la misma prioridad.
El origen del problema
En orden de prioridades esta:
- Importancia (!important)
- La especificidad
- Orden
El orden es el último de los factores a tomarse en cuenta. En un proyecto de desarrollo es común que los estilos se repitan porque hay elementos muy similares, con el enfoque de componentes esto se mitiga pero aun pasa.
La recomendación es usar CSS modules para evitar conflictos en elementos similares (al menos en nombres), usar alguna metodología para el naming de clases (como BEM) y metodlogía una de estructura (OOCSS). Nota: no necesariamente deben usar 2 a la vez, BEM es mas simple en cuestion de que organizamos por componentes, pero eventualmente llegamos a la conclusión de que debemos separar la estructura (layout, paddings, margins, características no visuales) de una capa de presentación (colores, bordes, elementos visuales).
Y el problema?
Bueno básicamente pese a tener todo eso, por alguna razón el framework que usabamos inyectaba los estilos en un orden extraño. Todas eran hojas de estilo externas, todos los estilos tenían la misma especificidad pero el orden de como se agregaban al bundle era diferente (no sabemos porque solo lo sabemos).
Imagina el siguiente ejemplo. Tengo un botón y este usa una variante que se llama Button--super que solo uso en un lugar por ende no hace sentido hacerla reusable (este mindset no siempre es válido).
Esa clase solo afecta el borde del botón, las otras clases no pueden eliminarse porque agregan estilos necesarios y de hacerlo tendría que duplicar código ademas de que no se agregarían updates a mi Button Super Saiyajin. Bueno sigamos con el código.
<button class="Button Button--default">My Button</button>
<button class="Button Button--default Button--super">My Super Button</button>
Y el CSS es el siguiente
/* En tu componente */
.Button--super {
border-color: yellow;
}
/* En el componente base */
.Button {
padding: 0.5rem;
}
.Button--default {
background: transparent;
font-weight: bold;
border-color: black;
}
Ahora considera que sea un componente crítico, puedes modificarlo pero en algunos casos puede haber snapshots que usen ese componente, alterarías el orden natural de las cosas tronando los tests y causando una marea de mensajes de slack justo a tu hora de la salida.
Bueno, tan simple como agregar un !important y ya, bueno eso sería algo simple resolveríamos el problema. Ahora imagina que en algun punto vas a definir otros N botones que cambien alguna de esas propiedades definidas en estilos que se estan sobreescribiendo por el orden en que se estan inyectando (no es sobreingeniería, eso va a pasar).
No puedes remover esos estilos y redefinir el componente claramente no es una opción por su uso, entonces ¿que haces? ¿llorar? (hice eso un rato)
Solución al problema
Bueno, ninguna solución esta libre de inconvenientes:
- Important, important everywhere: No
- Incrementar la especificidad: complicado si usas css modules
- Incrementar especificidad agregando data attributes: Me gusta pero cambiaría la estructura y no quiero eso
- Custom properties y usando los valores por default como fallback: me quedo con esta
¿Porque me gusta? Porque no cambio la estructura del componente, si uso un design system me es mucho más fácil definir el cambio de propiedades por temas en lugar usar un selector y puedo definir propiedades que se propagan mediante la cascada.
Es mucho más fácil cometer un error sobreescribiendo un estilo que una custom property.
No se si sea cierto, lo acabo de inventar XD, pero normalmente las custom properties de una aplicación se definen en el :root de una aplicación y este selector tiene una especificidad menor a la de una clase por ende podemos sobreescribirla usando clases especificas para nuestro componente.
Ojo, tampoco son infalibles, puedes cometer los mismos errores. Para evitar minimizarlos debes definir ciertas reglas.
Definir un prefijo para evitar conflictos en nombre de variables. Es caso de componentes core no hay tanto problema. En mi caso será
button-
.Tu componente core, en este caso el Botón solo usará fallbacks para definir el valor de su estilo, inclusive sus variantes y debe depender del valor de una custom property. Ejemplo:
.Button--default {
background: transparent;
font-weight: bold;
border-color: var(--button-border-color, black);
}
Si esa propiedad no esta definida en ese nodo ya sea que este definida mediante una clase o por herencia.
Al final puedes tener algo así y aunque la cascada quiere pasarte una mala jugada pues estás preparado.
.Button--super {
--button-border-color: yellow;
}
.Button {
padding: 0.5rem;
}
.Button--default {
background: transparent;
font-weight: bold;
border-color: var(--button-border-color, black);
}
No resolverá todos los problemas, pero te ayuda a pensar que partes de tu componente a nivel de estilos podrías exponer en una interfaz con la intención de que estuviera abierta a extensiones pero cerrado a modificaciones (SOLID eres tu? XD).
Una desventaja es que hay que recordar nombres de variables y puede ser una carga cognitiva pero nada es gratis en esta vida.
Si quieres ver este pequeño ejemplo funcionando te dejo el codepen.
BTW voy a estar publicando como usar @layer también evitar que tus estilos sean sobre escritos, en un futuro no muy lejano
Top comments (2)
Emocionante Joel, me encanta como cuentas historias y los memes
gracias :D