wandres.dev
SOPORTE Y DEGRADACIÓN · @supports y las capas de fallback

Escribir el camino base primero

La estrategia de mejora progresiva aplicada a @supports: qué va fuera de la condición, cómo se anula el estado base sin dejar restos, y un caso real con animaciones de scroll.

⏱ 18 min

La mejora progresiva se enseña como una actitud y se aplica como una técnica. La técnica cabe en una frase: fuera de la condición va algo que funciona solo, y dentro va lo que lo mejora. Lo difícil no es entenderla, es que exige diseñar el estado base a propósito en lugar de descubrirlo por accidente cuando alguien abre la página en el navegador equivocado.

🎯 Al terminar esta lección sabrás
  • Separar un componente en camino base y mejora sin dejar declaraciones huérfanas.
  • Anular desde la mejora todas las declaraciones del base que estorban.
  • Escribir un caso completo de animación dirigida por scroll con su fallback.
  • Ordenar mejoras y capas para que la cascada resuelva a favor de la mejora.

La regla y su parte incómoda

El camino base tiene que ser válido por sí mismo: no una versión rota, no un esqueleto, sino una versión que puedas enseñar sin disculparte. Esa es la parte que la gente se salta. Escribir un base que solo tiene sentido comparado con la mejora es exactamente lo mismo que no tener fallback, con más código.

La segunda parte de la regla es menos obvia: la mejora tiene que anular explícitamente todo lo que el base dejó puesto. Una condición no revierte nada; solo añade declaraciones. Si el base usa position: relative y padding-block-start para simular una proporción, y la mejora usa aspect-ratio, la mejora tiene que poner ese padding a cero, porque si no se suman.

/* base: funciona en cualquier motor */
.media {
  position: relative;
  padding-block-start: 56.25%;
  overflow: hidden;
}
.media > img {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* mejora: y deshace lo que el base necesitaba */
@supports (aspect-ratio: 16 / 9) {
  .media {
    padding-block-start: 0;
    aspect-ratio: 16 / 9;
  }
  .media > img {
    position: static;
    block-size: 100%;
  }
}

Un truco de revisión que funciona muy bien: comenta el bloque @supports entero y mira la página. Si lo que ves es aceptable, tienes mejora progresiva. Si lo que ves es basura, tienes dos implementaciones a medias y una de ellas se le va a servir a alguien.

Un caso real: animación dirigida por scroll

Este es el ejemplo canónico en 2026 porque el reparto de soporte es asimétrico y estable: las animaciones dirigidas por scroll están en Chromium y en Safari, y Firefox no las ha implementado. No es una carrera que vaya a resolverse sola en unas semanas, así que el fallback es código de producción, no una cortesía.

Una barra de progreso de lectura, primero en su versión completa:

@keyframes crecer {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* base: la barra existe, esta vacia y no molesta */
.progreso {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 3px;
  background: var(--acento);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

/* mejora: solo donde hay timeline de scroll */
@supports (animation-timeline: scroll()) {
  .progreso {
    animation: crecer linear;
    animation-timeline: scroll(root block);
  }
}

Fíjate en tres decisiones.

El estado base es scaleX(0), no scaleX(1). Sin soporte, la barra queda invisible y la página se ve exactamente como si no hubiera barra. Si el base fuera scaleX(1), Firefox mostraría una barra llena y permanente, que es peor que ninguna: comunica algo falso.

La animación entera vive dentro de la condición, incluida animation: crecer linear. Si animation estuviera fuera y solo animation-timeline dentro, un navegador sin timelines ejecutaría la animación con el reloj, y la barra se llenaría sola en el tiempo por defecto. Es el fallo más frecuente de este patrón y sale de repartir mal la frontera.

La condición pregunta por animation-timeline: scroll(), con la función dentro. Preguntar solo por la propiedad animation-timeline con otro valor detectaría menos de lo necesario.

Si la barra fuera importante para el producto, el fallback razonable sería una implementación con JavaScript y scroll con requestAnimationFrame, activada solo cuando la condición sea falsa, y eso se decide con CSS.supports('animation-timeline', 'scroll()'). Pero conviene ser honesto sobre el coste: un listener de scroll a cambio de una barra decorativa es un mal negocio, y la versión sin barra suele ser la respuesta correcta.

💡
El anclaje tiene el mismo reparto y otra frontera

El posicionamiento por ancla entró en Baseline cuando Firefox 147 lo implementó en enero de 2026, así que anchor-name y anchor() ya no necesitan condición en la mayoría de proyectos. Lo que sigue repartido es el fallback de posición: @position-try exige Safari 18.4 o superior. Y como @supports no sabe preguntar por at-rules, la detección se hace por la propiedad que las referencia: @supports (position-try-fallbacks: flip-block).

Orden, capas y cascada

@supports no aporta especificidad. Ni un punto. Un bloque condicional gana o pierde por las mismas reglas que cualquier otro: capa, especificidad del selector y orden de aparición.

De ahí salen dos disciplinas.

La mejora va después del base. Siempre. Un @supports colocado arriba del archivo, con el base debajo, evalúa a verdadero y no aplica nada, porque el base lo pisa. No hay error, no hay aviso, y el síntoma es “la condición no funciona en mi navegador” cuando la condición funciona perfectamente.

Los selectores tienen que coincidir. Si el base usa .media y la mejora usa .tarjeta .media, la mejora gana por especificidad y no por orden, lo cual funciona hasta que alguien reordena el archivo. Repetir exactamente el mismo selector deja el resultado dependiendo solo del orden, que es lo que quieres para poder razonar.

Con capas, la organización natural es poner las mejoras en una capa posterior:

@layer base, mejoras;

@layer base {
  .media { position: relative; padding-block-start: 56.25%; }
}

@layer mejoras {
  @supports (aspect-ratio: 1) {
    .media { padding-block-start: 0; aspect-ratio: 16 / 9; }
  }
}

Eso desacopla el orden físico del archivo del orden de la cascada, y permite mantener todas las mejoras juntas al final sin que la posición del texto importe.

El coste de mantener dos caminos

Cada bifurcación duplica lo que hay que probar. No al doble de trabajo total, porque muchas ramas comparten, pero sí al doble de estados posibles del componente. Con tres condiciones independientes tienes ocho combinaciones y probarás dos.

Tres formas de rebajar ese coste, en orden de preferencia.

No bifurcar. Buscar una solución que degrade sola. La mayoría de las características modernas están diseñadas para eso, y muchas veces el base y la mejora pueden ser la misma declaración.

Bifurcar arriba. Una sola condición que cubra un patrón completo es mucho más barata que cinco condiciones repartidas por el archivo, aunque el bloque sea más grande. La razón es que las combinaciones se multiplican y los bloques se suman.

Poner fecha. Un comentario con la razón concreta y la condición de retirada —qué navegador falta y en qué se notaría— convierte un bloque misterioso en una tarea. Sin eso, el bloque es permanente.

El camino base es el producto; la mejora es la propina

La trampa mental de la mejora progresiva es que casi todo el mundo la practica al revés sin darse cuenta: diseña la versión buena, la construye, y solo al final se pregunta qué hacer con los navegadores que no llegan. Ese orden garantiza que el fallback sea una amputación de algo, y una amputación siempre se nota. Cuando el orden es el correcto —diseñar primero lo que funciona en todas partes y después preguntarse qué se puede añadir donde haya más— pasa algo interesante: el base suele quedar mejor que la versión “completa” del otro método, porque ha tenido que resolver el problema con menos y eso obliga a decidir qué era esencial. La mejora, entonces, es un lujo real en lugar de una restauración. Hay un contraste que lo deja claro: nadie diseña una página asumiendo que las imágenes cargan y luego se pregunta qué hacer si no cargan; se escribe el alt porque la ausencia de imagen es un estado legítimo del documento. La ausencia de @supports merece exactamente el mismo tratamiento, y el día que lo tratas así dejas de escribir condiciones defensivas y empiezas a escribir dos diseños, uno de los cuales es más ambicioso que el otro.