La función random() de CSS trae imperfección controlada a tipografía, posiciones y animaciones. Dónde cabe…
El CSS moderno te quita líneas de código y te regala problemas que no sabías que podías tener
Artículo original en inglés: Modern CSS Might Make Your Front-End More Complex, de Gabriel Shoyombo en Smashing Magazine.
La paradoja de Le Corbusier con selectores
Le Corbusier prometió que la casa sería una máquina para vivir. Eliminó lo superfluo, redujo paredes, abrió planta libre. Lo que no dijo es que esa planta libre necesitaba una estructura de hormigón armado que triplicaba el presupuesto y un mantenimiento que nadie entendía. El CSS moderno está haciendo exactamente lo mismo: nos entrega herramientas que eliminan líneas de código de un archivo concreto mientras reparten complejidad invisible por todo el sistema. Y como la complejidad no tiene color de fondo, nadie la ve en el pull request.
Gabriel Shoyombo, desde Smashing Magazine, publica un artículo que debería imprimirse y colgarse en cada oficina donde alguien haya dicho “mira cuántas líneas me he ahorrado” como argumento definitivo. Su tesis es incómoda: una función CSS puede simplificar un fragmento de código y, al mismo tiempo, hacer que la aplicación entera sea más difícil de entender. No porque la función sea mala, sino porque confundimos simplicidad local con simplicidad total.
“Most of what we read about a new feature is written from the greenfield perspective. Most of what we ship is a migration.” — Gabriel Shoyombo
Cuatro formas de ahorrarte el código y regalarte el caos
Shoyombo no habla en abstracto. Presenta cuatro escenarios que cualquier equipo front-end reconocerá si es honesto consigo mismo.
El primero: JavaScript y CSS peleándose por el mismo territorio. Tienes un componente tarjeta cuyo ancho lo controlaba un ResizeObserver que añadía la clase .card--wide al llegar a 600 píxeles. Llegan las container queries y alguien añade una regla @container que también actúa a 600 píxeles. Ahora dos sistemas controlan lo mismo. Actualiza uno y olvida el otro: la tarjeta se comporta de forma distinta según qué lógica gane la carrera. Es como tener dos termostatos en la misma habitación conectados a calderas diferentes.
El segundo es más traicionero: container-type: inline-size no solo habilita las consultas, también cambia la geometría del elemento. Establece un contexto de formato de bloque, colapsa flotantes y suprime el colapso de márgenes. Traduzco: tu tarjeta puede pasar a medir 0 píxeles de alto sin que ningún test lo detecte, porque en CSS nadie escribe tests para márgenes colapsados. Es el equivalente arquitectónico de mover un tabique y descubrir que era un muro de carga.
El tercero tiene nombre propio: especificidad fantasma. El selector :has() combinado con nesting genera selectores compuestos de alta especificidad que alcanzan más lejos de lo previsto. Tocas el estilo de un formulario y se rompe una tarjeta en otra página que nunca has abierto. Es la versión CSS de aquel chiste sobre el programador que arregla un bug y crea otros tres.
El cuarto rompe un mito: mover trabajo de JavaScript a CSS no garantiza que sea más rápido. Shoyombo midió dos reglas :has() aparentemente inocuas con 5.000 nodos en el DOM. Por separado, cada una afectaba un elemento. Juntas, obligaban al navegador a recalcular el estilo de 5.001 elementos en cada interacción. A 20.000 nodos, el coste subía a 31 milisegundos por operación. Nadie revisa el rendimiento de un selector en una code review. Deberíamos.
El síndrome del segundo sistema, versión hojas de estilo
Fred Brooks lo describió en 1975 en The Mythical Man-Month: el segundo sistema siempre es el más peligroso, porque el equipo llega con la confianza del primero y la ambición de meter todo lo que se dejó fuera. Las container queries, :has() y el nesting nativo son nuestro segundo sistema. Llevábamos años pidiéndolos, y ahora que están aquí la tentación es usarlos todos a la vez, en cada componente, como el cocinero que acaba de descubrir la trufa y la pone hasta en el postre.
El dato que más me ha llamado la atención de la investigación es este: el 86% de los desarrolladores conocen las container queries, pero solo el 41% las usa. Esa brecha no es pereza; es prudencia disfrazada de inercia. Los equipos que mantienen aplicaciones grandes saben que adoptar una feature nueva no es escribir la regla nueva: es borrar la regla vieja, revisar que nada dependía de ella, asegurarse de que el nuevo comportamiento no colisiona con otra abstracción y documentar por qué se hizo el cambio. Eso es una migración, no un refactor de viernes por la tarde.
La simplicidad local es un espejismo si no se mide contra la complejidad global del sistema.
Seis preguntas antes de usar la feature nueva
La parte más útil del artículo es una lista de comprobación que Shoyombo propone antes de adoptar cualquier novedad de CSS en un proyecto existente:
1. ¿Quién manda? Si tanto JavaScript como CSS controlan el mismo comportamiento, uno sobra. Elige y borra el otro.
2. ¿Qué toca container-type? Al añadirlo, revisa qué pasa con el formato de bloque, los flotantes, los márgenes de los hijos y los vecinos. No asumas que solo habilita la consulta.
3. ¿Cómo interactúa con la cascada? Comprueba especificidad, capas (@layer) y orden de aparición. Un selector :has() anidado puede ganar batallas que no sabías que estaba librando.
4. ¿Has borrado el código viejo? Una migración no termina al escribir lo nuevo. Termina al eliminar lo antiguo por completo. Si la clase .card--wide sigue en el HTML, la migración no está hecha.
5. ¿Has medido el rendimiento con datos reales? No con tres tarjetas en localhost, sino con la cantidad de nodos que tiene tu aplicación en producción. La diferencia entre un selector inocuo y uno costoso solo aparece a escala.
6. ¿Has documentado el porqué? No qué hace la regla, sino por qué existe y dónde terminan sus efectos. El próximo que la toque necesita saber el radio de explosión.
El mapa y el territorio
Borges imaginó un imperio cuyos cartógrafos dibujaron un mapa tan detallado que terminó coincidiendo punto por punto con el territorio. Era inútil, porque reproducía la complejidad en lugar de reducirla. Un diff en rojo no es un mapa de mejora. Es una diferencia, y la diferencia puede ser para mejor o para peor dependiendo de lo que no se ve en la pantalla.
Las container queries son extraordinarias. :has() es probablemente el avance más importante en selectores CSS de la última década. El nesting nativo libera a miles de proyectos de la dependencia de Sass. Nada de eso está en discusión. Lo que Shoyombo pide, y hago mío, es que dejemos de evaluar las herramientas por la cantidad de líneas que eliminan y empecemos a evaluarlas por la cantidad de cosas que necesitas saber para que el sistema funcione al día siguiente. Porque la complejidad no desaparece al borrar líneas. Solo se muda de archivo.
Le Corbusier murió nadando en el Mediterráneo, lejos de sus máquinas para vivir. Sus edificios siguen en pie, pero la mayoría han necesitado reformas que él jamás previó. El CSS moderno es formidable. Solo necesita que le tengamos el mismo respeto que le tendríamos a un muro de carga: antes de moverlo, conviene saber qué sostiene.