wandres.dev
ARQUITECTURA II · Utility-first, CSS-in-JS y el estado del arte

El mejor argumento de cada lado

Lo que de verdad gana y pierde un motor de utilidades frente a CSS escrito a mano, presentado en su versión más fuerte y sin caricaturas, para que decidas tú.

⏱ 19 min

Este es el debate más ruidoso y peor argumentado del frontend, y lo es porque casi nadie discute contra la mejor versión del otro. Quien defiende las utilidades responde a “es CSS en línea”, que es falso; quien defiende el CSS tradicional responde a “el CSS a mano siempre acaba en caos”, que también es falso. Aquí están los dos argumentos en su forma más fuerte, con sus costes reales, y sin conclusión. La conclusión es tuya y depende de datos sobre tu equipo que yo no tengo.

🎯 Al terminar esta lección sabrás
  • Reconstruir el argumento a favor de las utilidades en su versión más sólida.
  • Reconstruir el argumento a favor del CSS tradicional en su versión más sólida.
  • Separar los costes reales de cada modelo de los costes imaginarios.
  • Identificar las tres preguntas sobre tu contexto que determinan la elección.

El mejor argumento a favor de las utilidades

No es la velocidad de escritura. Es este: el CSS tradicional tiene un problema de abstracción prematura que nadie ha resuelto en veinte años.

Cuando escribes .tarjeta { … }, estás afirmando que existe una cosa llamada tarjeta y que todas las tarjetas del producto comparten estos estilos. Casi siempre esa afirmación es falsa a los seis meses. Aparece la tarjeta del panel, que es igual pero sin sombra; la de la lista, con menos relleno; la del móvil, con otra disposición. Cada una llega como un modificador, y al cabo de dos años .tarjeta tiene once modificadores, tres de los cuales se anulan entre sí y ninguno se puede borrar porque nadie sabe quién los usa.

El origen del problema es que has creado una abstracción antes de saber si el patrón era real, y las abstracciones en CSS son especialmente caras de deshacer porque no hay tipos, no hay referencias y no hay refactorización automática. Renombrar una función en TypeScript es una operación segura; renombrar una clase en una hoja de estilos grande es una apuesta.

El modelo de utilidades ataca eso por la raíz: no crea ninguna abstracción hasta que decides crearla, y cuando la creas es un componente, no una clase. Y un componente sí tiene tipos, sí tiene referencias, sí se puede refactorizar con herramientas, y su uso se puede encontrar de forma exacta.

De ahí salen tres propiedades verificables:

El estilo es local por construcción. Cambiar las clases de un elemento no puede afectar a ningún otro elemento del producto. No hay acción a distancia. Eso elimina la clase entera de bugs donde tocas una regla y se rompe una pantalla que no habías abierto.

No hay código muerto. Borrar el componente borra su estilo. El problema que ninguna metodología semántica resolvió deja de existir, no porque se resuelva mejor, sino porque el CSS ya no tiene dueño.

El sistema de diseño lo hace cumplir el compilador. Los valores fuera de la escala requieren una sintaxis visible que la revisión detecta.

El mejor argumento a favor del CSS tradicional

No es la limpieza del marcado. Es este: el modelo de utilidades transfiere complejidad de un lenguaje diseñado para el problema a otro que no lo está, y esa transferencia tiene un techo.

CSS tiene mecanismos que existen porque el problema de estilar documentos los necesita: la herencia, la cascada, los contextos de formato, las consultas de contenedor, la especificidad como forma de expresar excepciones. Un motor de utilidades no elimina esos mecanismos, los deja intactos por debajo, pero te empuja a no usarlos, porque la unidad de trabajo es la declaración suelta sobre un elemento suelto.

La consecuencia se nota en cuanto el problema deja de ser “poner relleno a una caja”:

Las relaciones entre elementos se vuelven incómodas. “El párrafo que va después de un encabezado lleva menos margen superior” es una regla sobre una relación, no sobre un elemento. En CSS es un selector; en utilidades hay que ponerle una clase a cada párrafo que cumpla la condición, o recurrir a variantes que reintroducen selectores por la puerta de atrás con una sintaxis peor.

El estado compuesto se vuelve verboso. Un componente cuyo aspecto depende de tres condiciones tiene ocho combinaciones. En CSS eso es un puñado de reglas con :has() y custom properties. En utilidades es lógica en la plantilla que concatena cadenas, y esa lógica ya no está en el CSS: está en el JavaScript, donde nadie la busca cuando el bug es visual.

La densidad de información del marcado se desploma. No es una queja estética: es una propiedad medible. Una plantilla donde cada elemento arrastra treinta tokens de clase esconde su estructura. Leer el marcado para entender la jerarquía del documento —que es lo que haces al depurar accesibilidad, o al revisar semántica— se vuelve trabajo.

El conocimiento se vuelve intransferible. Aprender el vocabulario de un motor es aprender un dialecto que no sirve fuera. Aprender la cascada, el flujo y el layout sirve para siempre y en cualquier proyecto. Un equipo que solo sabe utilidades tiene un problema el día que hay que depurar por qué un margen colapsa, porque el modelo mental subyacente no se ha ejercitado nunca.

Y hay un argumento estructural, el más fuerte de todos: el CSS de 2026 ya resolvió el problema que motivó las utilidades. La escalada de especificidad la resuelve @layer. El ámbito lo resuelve @scope. Los valores duplicados los resuelven las custom properties. La adaptación al contexto la resuelven las container queries. La restricción de escala se puede hacer cumplir con tokens y un linter. Adoptar un compilador para resolver problemas que el lenguaje ya resuelve es asumir una dependencia y una capa de build a cambio de algo que podrías tener sin ellas.

ℹ️
Los dos argumentos son sobre cosas distintas

Fíjate en que no chocan de frente. El argumento a favor de las utilidades es sobre procesos de equipo a lo largo del tiempo: qué pasa cuando cuarenta personas editan la misma hoja durante cinco años. El argumento a favor del CSS tradicional es sobre la expresividad del lenguaje y la formación de quien lo escribe. Los dos pueden ser ciertos a la vez, y de hecho lo son. Por eso el debate no se resuelve con datos técnicos: se resuelve sabiendo cuál de los dos problemas tienes tú.

Costes reales frente a costes imaginarios

Media discusión se gasta en objeciones que no se sostienen. Conviene despejarlas para que quede solo lo que importa.

Objeción frecuente Estado
“Las utilidades son estilos en línea” falso: expresan :hover, media y container queries, pseudo-elementos
“El CSS generado es enorme” falso: crece con los valores distintos, no con las páginas
“El CSS tradicional siempre acaba en caos” falso: con @layer, @scope y revisión, no acaba en caos
“Con utilidades no se puede tener un sistema de diseño” falso: la escala del tema es el sistema
“El CSS tradicional es más lento de escribir” depende del proyecto; no es una propiedad del modelo

Y los costes que sí son reales, en ambas direcciones:

Modelo Coste real
Utilidades marcado ilegible en componentes complejos
Utilidades dependencia de una capa de build y de un proyecto externo
Utilidades reglas sobre relaciones entre elementos incómodas de expresar
Utilidades erosión del conocimiento del lenguaje subyacente
Tradicional requiere disciplina sostenida que degrada bajo presión
Tradicional no hay forma automática de detectar CSS muerto
Tradicional la abstracción prematura es el modo de fallo por defecto
Tradicional el coste de renombrar y refactorizar es alto y manual

Las tres preguntas que sí deciden

Si tienes que elegir hoy, estas tres preguntas discriminan mucho más que cualquier argumento general.

¿Cuánta rotación tiene tu equipo? La disciplina se transmite por convivencia. Un equipo estable de seis personas mantiene una convención durante años. Un equipo con rotación alta pierde la convención en cada ciclo, y ahí un compilador que la imponga vale mucho más que cualquier documento.

¿Tu marcado se genera desde componentes o se escribe a mano? La objeción de la repetición es fatal si el HTML se escribe a mano y casi desaparece si hay componentes. Un sitio con plantillas de servidor sin componentes reales es el peor caso para las utilidades. Una aplicación con componentes es el mejor.

¿Cuál es tu superficie de estilo por componente? Un producto con muchos componentes pequeños y poco estilo por componente encaja de forma natural con utilidades. Un producto con pocas vistas muy trabajadas —editorial, visualización de datos, animación compleja— tiene su complejidad dentro del CSS, y ahí las utilidades no ayudan: estorban.

La pregunta que sobrevive a las dos respuestas: dónde vive tu sistema de diseño

Si dentro de cinco años solo recuerdas una cosa de esta discusión, que sea esta, porque es lo único que no depende de qué herramienta gane. Los dos modelos funcionan cuando existe un sitio único y consultable donde están escritas las decisiones de diseño, y los dos fracasan igual de rápido cuando no existe. En el modelo tradicional ese sitio es la capa de tokens y el conjunto de componentes; el fracaso tiene la forma conocida de cuatrocientas variables sin jerarquía y treinta modificadores. En el modelo de utilidades ese sitio es el bloque de tema y el conjunto de componentes de plantilla; el fracaso tiene otra forma, menos comentada y más difícil de detectar: valores arbitrarios entre corchetes esparcidos por doscientos ficheros, cada uno perfectamente justificado en su momento, que juntos significan que ya no hay sistema. Nadie audita eso porque no hay ningún fichero que se haya hecho más grande; el desorden está distribuido en el marcado y es invisible en las estadísticas. Por eso el indicador de salud que de verdad importa no es qué herramienta usas, sino cuánto tarda un desarrollador nuevo en averiguar cuál es el color de acción primaria de tu producto. Si la respuesta está a un vistazo, el sistema está vivo con cualquier herramienta. Si la respuesta es “copia lo que hace el componente de al lado”, no tienes sistema, y ningún compilador te lo va a fabricar.