El precio de lo declarativo: qué ganas y qué renuncias
Idempotencia, tolerancia a fallos y recálculo incremental frente a la ausencia de control de flujo y de ámbito, y por qué esa ausencia es la razón de existir de toda la arquitectura CSS.
Un lenguaje declarativo compra propiedades muy valiosas a cambio de renuncias muy concretas, y CSS hizo ese trato de forma consciente. Lo que ganó —resultados que no dependen del orden de ejecución, tolerancia total al fallo, capacidad de recalcular solo lo afectado— es lo que permite que un documento con cien mil elementos se re-estile sesenta veces por segundo. Lo que renunció —control de flujo, funciones, ámbito— es la razón exacta de que exista la disciplina de arquitectura CSS. Esta lección cierra el nivel poniendo las dos columnas del trato una al lado de la otra.
- Enunciar las tres propiedades que CSS obtiene por ser declarativo.
- Relacionar la idempotencia con la posibilidad de recálculo incremental de estilos.
- Delimitar qué válvulas de escape existen y hasta dónde llegan.
- Identificar la ausencia de ámbito como el problema que motiva la cascada, las capas y las metodologías.
Lo que ganas
Idempotencia. Aplicar tu CSS dos veces da el mismo resultado que aplicarlo una. Dentro de una regla, el orden en que escribas las declaraciones de propiedades distintas es indiferente; entre reglas, el orden importa pero está definido por criterios explícitos, no por el instante en que se ejecutó nada. No hay estado acumulado, no hay efectos secundarios, no hay una sola línea de tu hoja de estilos cuyo resultado dependa de qué pasó antes en el tiempo.
Tolerancia total al fallo. Ya la viste en el nivel anterior: lo que el motor no entiende se descarta y el resto sigue. La consecuencia es que no existe el CSS roto, solo el CSS parcialmente aplicado. Ninguna hoja de estilos puede tumbar una página, y ninguna propiedad futura puede romper un navegador viejo. Compara con la alternativa: si CSS abortara al primer error, escribir para varios navegadores sería inviable y la web habría quedado congelada en el soporte del motor más lento.
Recálculo incremental. Esta es la que casi nadie conecta con las otras dos, y es la más importante. Como el resultado no depende de una historia de ejecución, el motor puede tomar un subárbol cualquiera, invalidar solo los elementos afectados por un cambio y recalcular únicamente esos, con la certeza de que el resultado será idéntico al que saldría de recalcular el documento entero. Sin idempotencia esa optimización sería incorrecta: habría que reproducir la secuencia completa desde el principio para garantizar el mismo estado.
Merece la pena ver la magnitud de lo que compra esa renuncia. Cuando cambias una clase en un elemento, el navegador no vuelve a evaluar todos los selectores contra todos los elementos: mantiene índices invertidos que le dicen qué reglas podrían verse afectadas por esa clase concreta, calcula un conjunto de invalidación mucho más pequeño que el documento y recalcula solo eso. Firefox va un paso más allá y reparte ese recálculo entre varios hilos, algo que sería imposible si el resultado de estilar un elemento dependiera del resultado de haber estilado otro antes. Esa arquitectura entera se apoya en una única garantía: que estilar un elemento es una función pura de ese elemento, sus ancestros y las reglas, sin memoria. En cuanto introduces control de flujo, contadores mutables o cualquier forma de estado compartido, la garantía se cae y con ella toda la optimización. Aquí está el matiz que da la vuelta a la queja habitual: cuando alguien lamenta que CSS no tenga bucles ni condicionales de verdad, está pidiendo cambiar la propiedad que hace posible que la página responda a sesenta fotogramas por segundo por comodidad al escribir. La comodidad al escribir es un problema resoluble en la herramienta de construcción; el rendimiento del recálculo, no. El trato está bien hecho, y la prueba es que los intentos históricos de meter imperatividad en las hojas de estilo, incluidas las expresiones dinámicas que hubo en su día en Internet Explorer, se abandonaron precisamente por esto.
Lo que no tienes
No hay control de flujo. No hay if, no hay bucles, no hay recursión. Lo más parecido a una condición son las reglas condicionales —@media, @supports, @container—, que no evalúan expresiones arbitrarias sino un conjunto cerrado de consultas sobre el entorno, el motor o el contenedor. Y lo más parecido a un bucle es repeat() en Grid, que no itera sobre nada: describe una repetición de pistas.
No hay funciones definidas por el usuario. Puedes usar calc(), min(), clamp(), color-mix(), pero no puedes definir una función propia que reciba parámetros y devuelva un valor. La consecuencia práctica es que la reutilización de lógica se hace con custom properties y con composición de reglas, no con abstracción.
No hay ámbito. Esta es la renuncia grande y la que de verdad duele. Todo selector que escribes es global: alcanza a cualquier elemento del documento que case, ahora o dentro de dos años, lo hayas escrito tú o lo haya insertado un componente de terceros. No hay módulos, no hay privado, no hay importación selectiva. @scope acota, pero es reciente y no es un sistema de módulos: es una restricción de subárbol.
No hay comprobación de tipos ni de nombres. Escribe colr: red y no pasa nada. Escribe una clase que no existe en ningún HTML y no pasa nada. Borra una clase del HTML y el CSS que la estilaba se queda ahí para siempre, sin que nada te avise. El CSS muerto es invisible por diseño.
Las válvulas de escape y hasta dónde llegan
CSS ha ido añadiendo mecanismos que alivian estas ausencias sin romper el modelo. Conviene saber exactamente hasta dónde llega cada uno, porque la frustración viene de pedirles lo que no pueden dar.
| Ausencia | Válvula | Límite real |
|---|---|---|
| Variables | Custom properties | Se heredan y viven en la cascada, no se pueden usar como nombres de propiedad |
| Condicionales | @media, @supports, @container |
Consultas de un conjunto cerrado, no expresiones libres |
| Abstracción | Anidamiento nativo | Ahorra repetición, no crea abstracción reutilizable |
| Cálculo | calc(), min(), max(), clamp() |
Aritmética sobre valores, sin ramificación |
| Ámbito | @scope |
Acota a un subárbol, no encapsula ni renombra |
| Composición de color | color-mix(), colores relativos |
Derivar colores, no definir funciones |
Las custom properties merecen un matiz porque se malinterpretan constantemente: no son variables de preprocesador. Una variable de Sass se sustituye antes de que exista el documento y desaparece; una custom property es una propiedad de verdad, que se hereda por el árbol, que se puede cambiar en tiempo de ejecución y que participa en la cascada como cualquier otra. Eso las hace mucho más potentes y también mucho menos predecibles, porque su valor depende del elemento en el que se lean.
El precio real: la arquitectura como respuesta a la falta de ámbito
Si tuviera que resumirse el trato en una frase: CSS te da robustez y rendimiento a cambio de un único espacio de nombres global, y todo lo que la industria llama “arquitectura CSS” es la respuesta acumulada a esa renuncia.
Vista así, la historia se ordena sola. BEM inventó una convención de nombres larga porque no había ámbito y hacía falta simular uno. Los módulos de CSS generaron nombres únicos en tiempo de construcción por la misma razón. CSS-in-JS movió el problema al lenguaje de la aplicación. Las utilidades atómicas lo esquivaron eliminando la necesidad de nombrar. Y ninguna de las cuatro es una moda gratuita: las cuatro atacan exactamente el mismo agujero.
Lo que cambió entre 2020 y 2026 es que el propio lenguaje empezó a tapar el agujero desde dentro. @layer te deja declarar autoridad sin tocar nombres. :where() te deja escribir reglas que no compiten. @scope acota de verdad. Y las custom properties permiten exponer puntos de extensión explícitos en lugar de confiar en que nadie sobrescriba tus selectores.
Por eso los cinco niveles siguientes de esta guía no van de propiedades visuales sino del sistema que decide qué valor gana. No es teoría previa al trabajo de verdad: es el trabajo de verdad. Empieza en la cascada, que es el algoritmo concreto que resuelve el conflicto que acabamos de describir.