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

Negación, combinación y anidamiento

Los tres operadores de @supports, los paréntesis que obliga la gramática, por qué not casi nunca es lo que quieres, y cómo anidar la condición dentro de una regla.

⏱ 15 min

La gramática de condiciones de @supports es diminuta: tres operadores y unos paréntesis. Aun así produce dos errores constantes, uno sintáctico que rompe el bloque entero en silencio y otro estratégico que convierte la mejora progresiva en código que nadie podrá borrar nunca. Los dos merecen una lección.

🎯 Al terminar esta lección sabrás
  • Combinar condiciones con and, or y not respetando la gramática.
  • Explicar por qué mezclar and y or exige paréntesis explícitos.
  • Justificar por qué una condición negada envejece peor que una positiva.
  • Anidar @supports dentro de una regla de estilo y dentro de @media.

Los tres operadores

@supports (display: grid) and (gap: 1rem)          { }
@supports (display: grid) or (display: flex)       { }
@supports not (display: grid)                      { }

and y or funcionan como esperas. not niega una condición completa, no una parte de ella: se aplica a lo que venga inmediatamente después, que tiene que ser una condición entre paréntesis o una función de condición.

Lo que no existe es una coma. En @media, la coma es un or histórico y funciona en todas partes; en @supports no hay coma, y escribirla invalida el preludio entero. Como una at-rule con preludio inválido se descarta con todo su contenido, el resultado es un bloque de estilos que desaparece sin ningún mensaje. Es el error más caro de esta lección porque no se parece a un error: se parece a que tu CSS no hace nada.

Los paréntesis que obliga la gramática

Esta regla no es una recomendación de estilo, es la gramática:

/* INVALIDO: no se puede mezclar and y or al mismo nivel */
@supports (a: b) and (c: d) or (e: f) { }

/* valido: el agrupamiento es explicito */
@supports ((a: b) and (c: d)) or (e: f) { }
@supports (a: b) and ((c: d) or (e: f)) { }

La especificación prohíbe mezclar operadores distintos sin agrupar precisamente para que no exista una regla de precedencia que haya que recordar. En @media pasa lo mismo desde el nivel 4. Es una decisión de diseño de las buenas: en lugar de definir que and liga más fuerte que or y confiar en que la gente lo sepa, hacen ilegal la ambigüedad.

Encadenar el mismo operador sí es legal y no necesita paréntesis extra:

@supports (a: b) and (c: d) and (e: f) { }

not y por qué casi nunca lo quieres

La negación es sintácticamente válida y estratégicamente casi siempre mala. El razonamiento tiene tres capas.

La primera: el orden ya te da la negación gratis. Si escribes el camino base fuera de cualquier condición y la mejora dentro de un @supports positivo, ya tienes las dos ramas, y la rama base la ve todo el mundo. No hace falta negar nada.

/* dos ramas, una negada: el estado base vive dentro de una condicion */
@supports not (aspect-ratio: 1) {
  .media { padding-block-start: 56.25%; position: relative; }
}
@supports (aspect-ratio: 1) {
  .media { aspect-ratio: 16 / 9; }
}

/* una rama: el estado base es incondicional */
.media { padding-block-start: 56.25%; position: relative; }
@supports (aspect-ratio: 1) {
  .media { aspect-ratio: 16 / 9; padding-block-start: 0; position: static; }
}

La segunda versión tiene una propiedad que la primera no: si algo falla en la evaluación de la condición, queda algo usable. En la primera, cualquier motivo por el que las dos condiciones evalúen a falso —un navegador que no entienda la función de condición, un error tipográfico en una de las dos, una versión antigua sin @supports— deja al elemento sin ninguno de los dos tratamientos.

La segunda capa: el bloque negado no se puede borrar. Cuando el soporte llega al 100%, el @supports positivo se puede eliminar subiendo su contenido un nivel; es una operación mecánica y segura. El bloque negado, en cambio, contiene el código antiguo, y borrarlo exige comprobar que ninguna de sus declaraciones era necesaria también en el camino nuevo. En la práctica nadie lo comprueba y esos bloques se quedan años.

La tercera: la doble negación es un truco muerto. El patrón @supports not (not (x)) se usaba para detectar navegadores que soportaban @supports pero no la característica, distinguiéndolos de los que no soportaban @supports en absoluto. En 2026 todos los motores relevantes tienen @supports desde hace más de una década y ese truco solo sirve para confundir a quien lea el código.

El único uso razonable de not es cuando el camino de fallback exige declaraciones que serían activamente dañinas con soporte: un float que rompería el grid, un margin negativo que descuadraría el gap. Y ni siquiera entonces es la primera opción; casi siempre se puede escribir la mejora de forma que las anule.

Anidar condiciones

@supports es una regla condicional de grupo, así que puede contener y ser contenida por otras at-rules, y desde el anidamiento nativo también puede vivir dentro de una regla de estilo.

.barra {
  position: sticky;
  inset-block-start: 0;

  @supports (backdrop-filter: blur(8px)) {
    background: color-mix(in srgb, canvas 70%, transparent);
    backdrop-filter: blur(8px);
  }
}

@media (width >= 48rem) {
  @supports (grid-template-columns: subgrid) {
    .ficha > * { grid-template-columns: subgrid; }
  }
}

Las dos formas son equivalentes en resultado; la anidada mantiene la mejora al lado de lo que mejora, que es la razón por la que casi siempre es preferible. El orden de anidamiento entre @media y @supports no cambia nada: ninguna de las dos aporta especificidad y las condiciones se evalúan de forma independiente.

Lo que sí importa es el orden de aparición, porque @supports no añade ni un punto de especificidad. Un bloque condicional que quiera ganar tiene que ir después del que sobrescribe, o vivir en una capa posterior. Un @supports colocado antes de la regla base no hace nada, y ese es el segundo bug silencioso de esta lección.

Una condición negada es una fecha de caducidad que nadie va a leer

Cuando escribes @supports not (x) estás afirmando algo sobre el futuro: que existe y seguirá existiendo un conjunto de navegadores sin x. Esa afirmación se vuelve falsa con el tiempo —siempre— y cuando lo hace, tu CSS no te avisa. El bloque sigue ahí, evaluando a falso en todas partes, ocupando sitio en el archivo, apareciendo en las búsquedas, confundiendo a quien lo lea y sobreviviendo a todas las refactorizaciones porque nadie se atreve a tocar código que no entiende del todo. Un @supports positivo tiene la propiedad opuesta: cuando la característica es universal, el bloque se vuelve siempre verdadero, y entonces cualquiera puede subir su contenido un nivel y borrar la condición sin pensar. Esa asimetría —una forma envejece hacia lo trivial de limpiar y la otra hacia lo imposible— es la razón real por la que la mejora progresiva se escribe en positivo, y es un criterio que se traslada tal cual a los feature flags, a las ramas por versión de API y a cualquier condicional que dependa del entorno. Escribe siempre la condición de forma que el paso del tiempo la convierta en trivialmente cierta, no en trivialmente falsa.