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

Lo que @supports no puede detectar

At-rules, características de media, descriptores y calidad de implementación: los cuatro huecos de la detección en CSS y qué técnica cubre cada uno.

⏱ 17 min

La gramática de @supports cubre declaraciones, selectores y capacidades de fuente. Todo lo demás que hay en CSS —at-rules, características de media, descriptores dentro de una at-rule, y por supuesto la calidad de una implementación— queda fuera, y no por descuido: preguntar por esas cosas exige un modelo de evaluación distinto. Saber dónde está la frontera evita el peor error de detección posible, que es escribir una condición que siempre da negativo y creer que estás protegiendo algo.

🎯 Al terminar esta lección sabrás
  • Enumerar las cuatro categorías que @supports no puede evaluar.
  • Detectar una característica de media desconocida con matchMedia y la propiedad media.
  • Reconocer cuándo la degradación automática hace innecesaria la detección.
  • Elegir entre CSS, JavaScript y no detectar según el tipo de característica.

Lo que la gramática no alcanza

La gramática de condiciones tiene producciones para declaraciones, para selectores y para fuentes. Todo lo que no encaje en una de esas tres formas cae en general enclosed y evalúa a falso, así que preguntar por ello no da error: da siempre negativo.

Las at-rules

No hay forma interoperable de preguntar en CSS si el navegador entiende @scope, @layer, @container, @position-try o @property. La especificación de CSS Conditional 5 define una función at-rule() para exactamente eso, pero solo la ha implementado Chromium y en una versión muy reciente. Usarla hoy como detección devuelve un negativo en Firefox y en Safari aunque soporten la at-rule perfectamente, que es el peor resultado posible: el fallback servido a navegadores capaces, de forma permanente y silenciosa.

La buena noticia es que casi nunca hace falta, porque las at-rules degradan mejor que las propiedades. Un navegador que no reconoce el preludio de una at-rule condicional descarta la regla entera con todo su contenido, sin aplicar nada a medias. Eso convierte la estrategia en la misma disciplina de siempre: escribe el estado por defecto fuera de la at-rule y usa la at-rule solo para modificarlo.

Cuando de verdad necesitas la respuesta —normalmente para decidir si cargas un polyfill— la vía es JavaScript, aprovechando que insertar una regla que no se analiza lanza o no inserta nada:

function soportaAtRule(texto) {
  const hoja = new CSSStyleSheet();
  try {
    hoja.insertRule(texto);
    return hoja.cssRules.length === 1;
  } catch {
    return false;
  }
}

soportaAtRule('@container (width > 10px) { .x { color: red } }');

Hay un matiz: algunas at-rules se insertan aunque su contenido no se entienda, así que la prueba tiene que ser lo bastante específica y conviene comprobar además alguna propiedad del objeto resultante.

Las características de media

@supports tampoco sabe preguntar si el navegador conoce una característica de media. No existe nada parecido a @supports media(prefers-reduced-transparency), y la razón es la misma que con las at-rules: la gramática de condiciones solo tiene productions para declaraciones, selectores y fuentes.

En CSS esto se resuelve solo, porque una @media con una característica desconocida evalúa a falso y no aplica su contenido. El patrón es idéntico: base fuera, ajuste dentro.

Desde JavaScript sí hay una detección exacta, y es de las pocas cosas realmente elegantes de la API de media queries. Cuando matchMedia recibe una consulta que no sabe analizar, normaliza la propiedad media a la cadena not all:

function soportaCaracteristica(consulta) {
  return matchMedia(consulta).media !== 'not all';
}

soportaCaracteristica('(prefers-reduced-motion: reduce)');   // true
soportaCaracteristica('(caracteristica-inventada: 1)');      // false

Esa distinción es importante porque matches vale false en dos situaciones muy distintas: la consulta se entiende y no se cumple, o la consulta no se entiende. La propiedad media es lo único que las separa.

Los descriptores

@supports evalúa declaraciones de regla de estilo. Un descriptor dentro de una at-rule no es una declaración de regla de estilo, aunque se le parezca mucho.

@font-face {
  font-family: 'Texto';
  src: url('texto.woff2') format('woff2');
  size-adjust: 105%;
  ascent-override: 92%;
}

Ni size-adjust ni ascent-override se pueden detectar con @supports. Peor: @supports (size-adjust: 105%) no es que dé negativo por prudencia, es que evalúa como general enclosed —sintaxis desconocida en contexto de declaración— y siempre da falso, incluso en un navegador que soporte el descriptor de sobra. La at-rule at-rule() de Conditional 5 prevé una forma con descriptor para cubrir esto, y le pasa lo mismo que a todo lo demás de esa función: no hay soporte interoperable.

Este hueco tiene consecuencias prácticas concretas, porque el reparto de soporte de los descriptores de métricas es real: size-adjust está en los tres motores, mientras que ascent-override, descent-override y line-gap-override están en Chromium y Firefox y no en Safari. Como no se pueden detectar, la única estrategia posible es que el resultado sin ellos sea aceptable, no que sea distinto.

Cuando la información hace falta de verdad, la respuesta está en la API de fuentes: document.fonts y el constructor FontFace sí exponen los descriptores como propiedades del objeto, y comprobar si una asignación se conserva es una detección real.

La calidad de la implementación

Este es el hueco grande y el que no tiene técnica. @supports responde sobre el analizador; los bugs viven en el resto del motor. Un navegador puede analizar una propiedad y aplicarla mal, aplicarla solo en un tipo de contenedor, ignorarla dentro de un contexto de formato concreto, o tener un rendimiento inaceptable para tu caso.

Hay una variante de este problema que aparece constantemente y conviene nombrar: el soporte parcial por contexto. Una propiedad puede estar implementada para un elemento y no para otro, o funcionar en el flujo normal y no dentro de un contenedor de un tipo concreto. La condición no distingue contextos porque analiza la declaración aislada.

La única herramienta que cubre esto es probar en el navegador real. Los datos de tablas de compatibilidad ayudan a saber dónde mirar, pero no sustituyen a abrirlo.

⚠️
Una condición que siempre da falso no se distingue de una que funciona

De los cuatro huecos, tres comparten el mismo modo de fallo: la condición no da error, da falso. Y como el camino de fallback suele estar razonablemente bien, la página se ve casi bien, con lo cual nadie investiga. Si escribes una detección nueva, la comprobación mínima es abrir la consola y ejecutar CSS.supports() con la misma cadena en un navegador que sí tenga la característica. Si devuelve false ahí, la condición está mal escrita, no el navegador.

Cuándo la respuesta correcta es no detectar

Repasando la frontera aparece un patrón: los huecos de @supports coinciden casi exactamente con las partes de CSS que degradan solas y bien. No es casualidad. Las at-rules descartan su contenido entero, las consultas de media desconocidas evalúan a falso, los descriptores no reconocidos se ignoran dejando la fuente utilizable. El grupo de la CSS Working Group diseña la degradación primero y la detección después, precisamente para que la detección sea el último recurso.

El árbol de decisión que queda es corto:

  • Si el navegador va a descartar la declaración y lo que queda es aceptable, no detectes nada.
  • Si la mejora son varias declaraciones que solo tienen sentido juntas, o toca otra propiedad distinta, usa @supports en positivo.
  • Si necesitas la respuesta para cargar código, usa CSS.supports() o la detección por insertRule o matchMedia.
  • Si lo que te preocupa es un bug concreto de un motor, no hay detección; hay una decisión de producto sobre a qué navegador le sirves qué.
La detección de características es deuda que se paga en el futuro, no en el presente

Hay una simetría curiosa entre la detección de características en CSS y en JavaScript, y explica por qué en CSS se usa tan poco. En JS, una API que no existe lanza una excepción y se lleva por delante el resto de la función; el coste de no detectar es catastrófico e inmediato, así que detectar es obligatorio. En CSS, una declaración que no existe se descarta y no afecta a nada más; el coste de no detectar es que falta un efecto. Esa diferencia de modelo de fallo es la razón profunda de que CSS pueda permitirse una detección tan limitada, y también la razón de que copiar la cultura de detección de JS a CSS produzca hojas de estilo llenas de condiciones que no protegen de nada. Cuando dudes, mide el coste real de no detectar en ese caso concreto —no el coste imaginario— y compáralo con el coste, muy real, de mantener dos caminos durante los cinco años que va a vivir el proyecto. La respuesta suele sorprender: el estado degradado casi siempre es más barato de aceptar que de evitar, y el equipo que asume eso escribe menos CSS, prueba menos combinaciones y se enfrenta a menos regresiones cuando el soporte por fin llega y hay que limpiar.