Los errores que ya no deberías cometer
Veinte prácticas que fueron razonables en su momento y hoy son deuda: qué las justificaba, qué las sustituye, y cómo detectarlas en un proyecto que ya existe.
Casi ninguna de estas prácticas fue nunca estúpida. Todas resolvían algo real en su momento, se propagaron porque funcionaban, y sobrevivieron a la desaparición de su justificación porque nadie vuelve a revisar lo que ya funciona. Esta es la lista de las que en 2026 tienen un sustituto claro y mejor, con el motivo por el que existían —que es lo que hace que puedas juzgarlas en lugar de obedecerlas— y con la señal para encontrarlas en un proyecto heredado.
- Identificar veinte prácticas obsoletas con su sustituto exacto.
- Explicar qué las justificaba, para poder juzgar casos parecidos.
- Auditar un proyecto existente buscando sus señales concretas.
- Distinguir la deuda que hay que pagar de la que conviene dejar en paz.
En la cascada y en los valores
En la cascada
Ganar con especificidad. Escribir .pagina .barra .boton para vencer a .boton. Se justificaba porque no había otra forma de expresar prioridad. Hoy es @layer, que separa el orden de la especificidad. Señal: selectores de tres o más clases donde una bastaría.
!important para que algo se aplique. Se justificaba como último recurso en la escalada. Hoy, salvo utilidades heredadas y preferencias de accesibilidad, es señal de que falta una capa. Señal: buscar !important en el proyecto y contar; más de una docena es un problema de arquitectura, no de disciplina.
Depender del orden de los ficheros. Se justificaba porque era el único mecanismo disponible. Hoy es frágil ante la división de código y la carga diferida, y @layer lo hace irrelevante. Señal: un comentario en el fichero de entrada explicando que el orden de los @import importa.
Prefijar todas las clases con el nombre del bloque. Se justificaba como sustituto del ámbito. Hoy es @scope o cualquier herramienta que genere nombres únicos. Señal: nombres de clase de más de treinta caracteres. Matiz importante: si el proyecto ya está así y funciona, no lo migres; el coste de una migración de nombres no lo compensa nada.
Selectores de identificador para estilar. Se justificaba por comodidad. Hoy es un valor de especificidad que no se puede vencer sin escalar. Señal: un # en cualquier selector de una hoja de estilos.
En los valores
Variables de preprocesador para valores de diseño. Se justificaba porque las custom properties no existían. Hoy pierdes la cascada, los subárboles, el runtime y la posibilidad de tema. Señal: un fichero de variables que se compila y desaparece.
Colores en hexadecimal para una paleta. Se justificaba porque no había alternativa perceptual. Hoy OKLCH permite construir rampas donde el paso de claridad es constante y las mezclas no pasan por gris. Señal: una paleta donde dos colores con la misma “intensidad nominal” se ven claramente distintos de intensidad.
Escribir el mismo valor en más de un sitio. Se justificaba porque no había indirección. Hoy es un token. Señal: buscar un color de marca y encontrarlo más de una vez fuera de la capa de tokens.
Media queries para tipografía en pasos. Se justificaba porque no había interpolación. Hoy es clamp() con un tramo fluido. Señal: tres media queries seguidas cambiando el mismo font-size.
Bajar la opacidad para atenuar texto. Se justificaba por comodidad. Hoy produce contrastes distintos según el fondo, y en modo oscuro incumple, como se vio en el nivel 52. Señal: opacity: 0.6 sobre un elemento de texto.
En el layout
Posicionamiento absoluto para colocar. Se justificaba porque no había layout bidimensional. Hoy es Grid, y la diferencia no es de comodidad: lo absoluto no reserva espacio, así que nada se adapta. Señal: position: absolute con coordenadas en porcentaje sobre un elemento que forma parte del flujo visual.
Márgenes negativos para compensar espaciado. Se justificaba porque no había gap. Hoy gap reparte sin colapsos ni compensaciones. Señal: margin: -8px en un contenedor con padding: 8px en los hijos.
Alturas fijas para alinear. Se justificaba porque el alineado vertical era imposible. Hoy es una línea de align-items. Señal: una altura en píxeles sobre un elemento que contiene texto, que es una bomba de relojería en cuanto el texto se traduce.
Clases de punto de ruptura sobre los componentes. Se justificaba porque solo se podía consultar la ventana. Hoy es @container, y el componente deja de saber dónde está. Señal: una clase con nombre de tamaño de pantalla aplicada a un componente.
overflow-x: hidden en body. Se justificaba como parche universal para un desbordamiento cuya causa nadie encontró. Hoy sigue siendo un parche que rompe el anclaje de scroll y el desplazamiento programático. Señal: literalmente esa declaración. La causa se encuentra con el método del nivel 54.
En la interacción y el estado
Clases de estado sincronizadas desde JavaScript. Se justificaba porque CSS no podía consultar el estado de un hijo. Hoy es :has() con las pseudo-clases nativas. Señal: código que añade y quita una clase en respuesta a un evento de foco, de hover o de validación.
:invalid para estilar errores. Se justificaba porque :user-invalid no existía. Hoy produce el formulario que grita antes de que escribas nada. Señal: :invalid en cualquier hoja escrita después de 2023.
outline: none sin sustituto. Nunca se justificó, y sin embargo está en casi todos los resets antiguos. Hoy :focus-visible da exactamente lo que la gente quería —no ver el aro al hacer clic, verlo al tabular— sin quitárselo a nadie. Señal: outline: 0 o outline: none en cualquier sitio.
z-index en escalada. Se justificaba por no entender los contextos de apilamiento. Hoy, además, la capa superior con dialog y popover elimina la necesidad para todo lo que flota. Señal: cualquier z-index de cuatro o más cifras.
will-change permanente en la hoja de estilos. Se justificaba como optimización preventiva. Hoy sabemos que reserva memoria de GPU que nunca se libera y desactiva las heurísticas del navegador, como se detalló en el nivel 53. Señal: will-change en una regla que no se activa y desactiva.
Tres cosas de esta lista no merecen una migración: los nombres al estilo BEM en un proyecto que ya los tiene, los prefijos de vendedor que siguen haciendo falta en algún control de formulario, y cualquier práctica obsoleta que esté en un módulo que nadie toca. La regla es sencilla y ahorra muchísimo tiempo: paga deuda en el código que estás editando de todas formas. Una migración de estilo sin cambio funcional no tiene tests que la respalden y el riesgo de regresión visual es real.
Auditar un proyecto en diez minutos
Una secuencia de búsquedas que da una radiografía sorprendentemente buena:
# escalada de especificidad
grep -rn "!important" src/ | wc -l
grep -rEn "#[a-zA-Z][-_a-zA-Z0-9]*\s*[,{]" src/
# valores duplicados fuera de la capa de tokens
grep -rn "#[0-9a-fA-F]\{6\}" src/ | wc -l
# parches conocidos
grep -rn "overflow-x: *hidden" src/
grep -rn "outline: *\(none\|0\)" src/
grep -rEn "z-index: *[0-9]{4,}" src/
grep -rn "will-change" src/
# estado que deberia estar en CSS
grep -rn "classList.\(add\|remove\|toggle\)" src/ | wc -l
Ninguno de esos números es un veredicto por sí solo. Lo que informa es la proporción: cuarenta !important en un proyecto de mil líneas es una arquitectura rota; cuarenta en uno de cien mil puede ser normal. Y el último es el más revelador: si el proyecto añade y quita muchas clases desde JavaScript, hay un conjunto de estados que podrían vivir en CSS y no lo hacen.
Hay una tentación al leer una lista así, y conviene resistirla porque es la que impide aprender de ella: sentirse superior a quien escribió ese código. Cada una de estas veinte prácticas fue, en su momento, la mejor solución disponible a un problema real, escrita por gente que sabía exactamente lo que hacía y que a menudo documentó por qué. !important en las utilidades no era pereza: era la única forma de expresar “esto debe ganar” cuando no existían las capas. Los nombres de treinta caracteres no eran barroquismo: eran ámbito implementado a mano cuando no había ámbito. Las alturas fijas no eran torpeza: eran lo que quedaba cuando el alineado vertical no existía. Lo que las convirtió en errores no fue un cambio de opinión sino la aparición del mecanismo que faltaba, y ese es el patrón que hay que llevarse: una práctica no envejece por sí sola, envejece cuando el sustrato la deja atrás. De ahí sale la única conclusión que de verdad importa, y es incómoda porque apunta hacia ti. Todo lo que estás escribiendo hoy con más disciplina de la que te gustaría —cada convención que tu equipo repite en las revisiones, cada patrón que hay que recordar aplicar, cada compensación que solo funciona porque alguien la vigila— es un candidato exacto a aparecer en esta lista dentro de cinco años, cuando el mecanismo que le falta llegue al lenguaje. No puedes evitarlo y no deberías intentarlo: lo que sí puedes hacer es escribir junto a cada una por qué está ahí. Un comentario de una línea que diga qué problema resuelve convierte una práctica obsoleta en algo que la siguiente persona puede evaluar y retirar en dos minutos, en lugar de en un fósil que nadie se atreve a tocar durante ocho años.