CSS if(): condicionales intercalados para estilos más inteligentes

  • if() permite lógica condicional en valores CSS con style(), media() y supports().
  • Evalúa condiciones en orden; la primera verdadera devuelve su valor y else cubre el resto.
  • Ideal para variar una propiedad sin abrir @media/@supports; usa fallbacks para compatibilidad.
  • Combina con custom properties y data-attributes para componentes más autónomos.

Qué son los selectores de atributo en CSS

Durante años dimos por hecho que CSS no podía “pensar”. Aun así, teníamos apaños: @media, @supports, @container y las variables con fallback nos permitían cambiar estilos según el contexto. Ahora llega una novedad que sube el listón y encaja mejor con cómo razonamos al maquetar: la función condicional if() directamente en los valores de las propiedades.

No es humo: a partir de Chrome 137 puedes probar if() para crear estilos intercalados (inline) con lógica condicional basada en consultas de estilo, de medios y de soporte. Eso sí, el soporte aún es limitado fuera de Chromium y conviene usarla con cabeza, combinándola con fallbacks para navegadores que no la entienden.

Qué había antes: “condicionales” que ya usábamos en CSS

Antes de if(), CSS ya ofrecía mecanismos condicionales, cada uno con su ámbito. No son if verdaderos, pero cumplen en sus terrenos:

  • @media: aplica reglas si la ventana o el dispositivo cumplen un criterio (ancho, orientación, puntero, etc.).
  • @supports: activa estilos si el navegador entiende una característica o valor.
  • @container: reacciona al tamaño o estilo de un contenedor, útil para diseño basado en componentes.
  • var() con valor por defecto: no es un if, pero permite “si no existe la variable, usa este fallback”.

Estos enfoques son potentes, aunque están “pegados” a su finalidad: medios, soporte o contenedores. La función if() no reemplaza estas herramientas; más bien las integra de forma intercalada y granular en los valores de las propiedades.

Qué es if() en CSS y por qué importa

Diseño web

La función if() permite definir, dentro del valor de una propiedad, una lista de parejas condición:valor, evaluadas en orden. Cuando la primera condición sea verdadera, su valor asociado será el que “gane”. Es un flujo similar a if…else de JS, pero embebido en CSS.

En su versión actual, if() entiende tres tipos de consultas dentro de sus condiciones: style() (consultas de estilo sobre el propio elemento), media() (consultas de medios) y supports() (consultas de soporte de características). Además, puedes añadir un else: valor para cubrir el caso en que nada más encaje.

Sintaxis detallada y evaluación

La estructura general es una lista separada por punto y coma de ramas del tipo <if-condition>: <value>. Cada condición es o bien un <if-test> (style(), media() o supports()) o la palabra clave else, que siempre evalúa a verdadero.

/* Estructura básica */
propiedad: if(
  style(--estado: activo): valor-1;
  media(width < 700px): valor-2;
  supports(color: lch(60% 50 40)): valor-3;
  else: valor-por-defecto
);

El motor evalúa las ramas en orden. La primera que sea true devuelve su valor. Si ninguna condición es verdadera y tampoco hay else, el resultado de la función es guaranteed-invalid, que se comporta como un valor inválido y permite que entren en juego fallbacks externos (por ejemplo, el valor por defecto de una propiedad personalizada o un valor previo en cascada).

Un detalle clave: si una condición o un valor concreto dentro de la lista es inválido, no invalida toda la función; el parser continúa con la siguiente pareja. Esto hace a if() resistente a fallos puntuales dentro de las ramas.

Tipos de pruebas: style(), media() y supports()

style(): consultas de estilo

Las consultas de estilo con style() permiten preguntar si el elemento objetivo tiene un determinado valor para una propiedad (especialmente útil con propiedades personalizadas). A diferencia de las consultas de estilo de @container, style() en if() se evalúa sobre el propio elemento y de forma inmediata, sin necesidad de depender de un padre.

/* Estado embebido en una custom property */
.card {
  --status: attr(data-status type(<custom-ident>));
  border-color: if(
    style(--status: pending): royalblue;
    style(--status: complete): seagreen;
    else: gray
  );
  background-color: if(
    style(--status: pending): #eff7fa;
    style(--status: complete): #f6fff6;
    else: #f7f7f7
  );
}

Dentro de style() puedes usar operadores lógicos and, or y not, además de paréntesis para agrupar, lo que facilita combinaciones complejas de estados. Ten presente que las consultas de estilo de @container hoy no admiten propiedades “normales” (solo personalizadas), mientras que style() dentro de if() se usa para decidir el valor de una única propiedad en el propio elemento.

media(): consultas de medios intercaladas

Con media() puedes condicionar el valor de una propiedad con una media query específica, sin separar el estilo en otro bloque @media. Es ideal cuando solo quieres variar una propiedad según el entorno.

/* Ejemplo para puntero fino vs grueso */
button {
  aspect-ratio: 1;
  width: if(
    media(any-pointer: fine): 30px;
    else: 44px
  );
}

Este bloque es equivalente a escribir una regla base más un @media que redefine la propiedad, pero concentra la lógica en un único sitio. También puedes usar media types (por ejemplo, print) o media features con lógica and/or/not.

/* Media type */
.elemento {
  background: if(
    media(print): white;
    else: #eee
  );
}

/* Media feature con rango */
.caja {
  margin: if(
    media(width < 700px): 0 auto;
    else: 20px auto
  );
}

Si necesitas cambiar muchos selectores o varias propiedades a la vez, sigue siendo mejor un bloque @media tradicional. Para variaciones puntuales en una única declaración, if() resulta más expresivo.

supports(): consultas de soporte

Con supports() preguntas si el navegador entiende una característica concreta (propiedad, valor, selector, etc.) y eliges el valor en consecuencia. Perfecto para progresive enhancement sin duplicar bloques @supports.

/* Color de amplia gama si está soportado */
body {
  background-color: if(
    supports(color: oklch(0.7 0.185 232)): oklch(0.7 0.185 232);
    else: #00adf3
  );
}

/* Consulta de soporte de selector */
video {
  outline-width: if(
    supports(selector(:buffering)): 1em;
    else: initial
  );
}

Recuerda que para que una rama con supports() dentro de if() funcione, el propio navegador debe soportar if(). De ahí la importancia de definir fallbacks sólidos fuera de la función.

Frecuencia y posición de else

Puedes usar uno o varios else dentro de la función y situarlos donde quieras. No obstante, lo más común es colocar un único else al final como valor por defecto. Si pones else al principio, siempre ganará y las condiciones posteriores no se evaluarán.

/* Útil para depurar: colocamos un else intermedio */
.elemento {
  background-image: if(
    style(--flag: on): url("ok.png");
    else: url("debug.png"); /* si la primera no encaja, mostramos pista */
    supports(background: paint(foo)): paint(foo)
  );
}

Además, una función if() compuesta solo por else: valor, o incluso vacía, es sintácticamente válida, aunque poco útil. Mejor asignar el valor directamente si no hay lógica.

Valores completos, parciales y anidación

La función puede ocupar todo el valor de una propiedad o solo una parte dentro de un shorthand. Esto habilita composiciones muy expresivas sin salirte de la misma declaración, con menos repetición de código.

/* Decidimos solo el color dentro de un shorthand */
.box {
  border: 2px solid if(
    supports(color: lch(60% 50 40)): lch(60% 50 40 / 0.7);
    else: rgb(100 120 200 / 0.7)
  );
}

Las funciones if() pueden anidarse dentro de otras if(), y también en funciones como calc(). Esto permite modelar decisiones más finas sin duplicar selectores ni abrir nuevos bloques.

/* Anidación doble: esquema y preferencia de color */
.elemento {
  color: if(
    style(--scheme: ice): if(
      media(prefers-color-scheme: dark): oklch(85% 0.04 220);
      else: oklch(35% 0.04 220)
    );
    style(--scheme: fire): if(
      media(prefers-color-scheme: dark): oklch(90% 0.1 30);
      else: oklch(40% 0.1 30)
    );
    else: black
  );
}

/* calc() con porcentaje condicionado */
.panel {
  width: calc(
    if(style(--scheme: wide): 70%; else: 50%) - 50px
  );
}

También podemos usar if() para decidir partes sueltas de una propiedad como margin-top dentro de un shorthand, o una componente concreta de background, sin duplicar lo que no cambia.

Ejemplos prácticos intercalados

1) Botón accesible según el tipo de puntero

Si el dispositivo tiene un puntero fino (por ejemplo, ratón), el botón será más pequeño; en pantallas táctiles, subimos a 44px para cumplir con recomendaciones de accesibilidad. Una sola declaración, dos comportamientos.

button {
  aspect-ratio: 1;
  width: if(
    media(any-pointer: fine): 30px;
    else: 44px
  );
}

Lo anterior equivale a una base + un bloque @media, pero así no repartes la lógica en varios sitios y reduces la posibilidad de errores.

2) Colores de amplia gama con fallback

Si el navegador entiende OKLCH, usamos ese color y, ya que estamos, mostramos un mensaje informativo en ::after. En caso contrario, aplicamos un hex seguro. Compatibilidad sin dramas.

body {
  background-color: if(
    supports(color: oklch(0.7 0.185 232)): oklch(0.7 0.185 232);
    else: #00adf3
  );
}

body::after {
  content: if(
    supports(color: oklch(0.7 0.185 232)): "Tu navegador admite OKLCH";
    else: "Tu navegador no admite OKLCH"
  );
}

Si quieres explorar la diferencia visual entre RGB y espacios modernos, echa un ojo a recursos como oklch.com. Verás por qué compensa adoptar estos espacios cuando haya soporte.

3) Tarjetas que cambian por estado de IU

Almacena el estado en data-status, léelo con attr() hacia una custom property y toma decisiones con style(). Una vía elegante para componentes auto-contenidos.

<div class="card" data-status="complete">...</div>

.card {
  --status: attr(data-status type(<custom-ident>));
  border-color: if(
    style(--status: pending): royalblue;
    style(--status: complete): seagreen;
    else: gray
  );
  background-color: if(
    style(--status: pending): #eff7fa;
    style(--status: complete): #f6fff6;
    else: #f7f7f7
  );
}

Con este patrón, cambiar el data-atributo en HTML altera múltiples propiedades sin tocar CSS extra ni JS de clase-toggling.

Else y orden de evaluación: cómo piensa if()

Las condiciones se evalúan en el orden en que aparecen. La primera verdadera devuelve el valor y se detiene la evaluación. Si ninguna pasa, el resultado será guaranteed-invalid, lo que deja paso a fallbacks externos (por ejemplo, un valor anterior en cascada o el valor por defecto del navegador).

También puedes colocar else en medio para depurar: si quieres saber si una primera condición no está funcionando, sitúa un else que devuelva, por ejemplo, una imagen “debug” y así visualizas el problema con claridad.

Fallbacks para navegadores sin if()

if() no hace degradación automática por arte de magia: necesitas un valor de reserva. Pon primero una declaración segura para todos los navegadores, y después, la versión con if() que la sobrescriba cuando esté soportada. Es el patrón recomendado.

.box {
  padding: 16px; /* Fallback para navegadores sin if() */
  padding: if(
    style(--size: "2xl"): 24px;
    else: 16px
  );
}

Así, los navegadores sin if() usarán la primera línea; los que sí lo soportan evaluarán la segunda y la sustituirán.

Compatibilidad y estado actual

Programación

Según la información más reciente facilitada, Chrome 137 ya permite probar if() y Edge en su versión equivalente hereda el soporte. Otras fuentes recuerdan que, fuera de Chromium, la implementación todavía no está extendida y que la propuesta seguirá madurando en la W3C, por lo que conviene mantener expectativas realistas.

Algunas estimaciones hablan de alrededor de un ~47% de cobertura por ahora (en el momento de su lanzamiento inicial). Mi recomendación es usarla en producción solo si controlas el entorno o si tu estrategia de fallback es sólida; de lo contrario, aplícala en capas progresivas o en entornos controlados.

Relación con @media, @supports y @container

if() y las reglas at son complementarias, no excluyentes. @media o @supports siguen siendo mejores cuando quieres aplicar conjuntos de reglas completos a varias propiedades/seletores. if() brilla cuando necesitas una decisión puntual en un único valor, manteniendo la lógica pegada a la propiedad, lo que facilita el mantenimiento.

En diseño orientado a componentes, la dupla de @container (para responder al layout del contenedor) e if() con style() (para variar propiedades específicas del propio componente) abre flujos muy potentes y menos verbosos.

La propuesta @when y @else

Además de if(), hay propuestas como @when / @else que plantean condicionales a nivel de reglas, con una sintaxis pensada para leer mejor condiciones complejas que, con @media y @supports encadenados, se vuelven difíciles de mantener.

@when media(width <= 600px) {
  .container { display: flex; flex-direction: column; }
}
@else {
  .container { display: flex; flex-direction: row; }
}

Puedes combinar and, or, not y usar paréntesis para agrupar. En casos triviales, un @media basta, pero en combinaciones más ricas @when/@else gana legibilidad.

@when media(width <= 600px) and supports(display: grid) {
  /* ... */
}
@else media(width >= 600px) and supports(display: flex) {
  /* ... */
}
@else {
  /* Fallback final */
}

Estas reglas siguen en evolución y su implementación real puede tardar, pero apuntan a simplificar condicionales extensos a nivel de hoja de estilos.

Patrones, límites y consejos

  • Mantén las ramas ordenadas por probabilidad y claridad. La primera verdadera gana, así que ordena pensando en rendimiento y legibilidad.
  • Usa else final como valor por defecto; si lo adelantas, cortas la evaluación del resto de condiciones.
  • Recuerda que if() decide una sola propiedad cada vez. Si hay que cambiar muchas cosas a la vez, valora @media/@supports o dividir en varias declaraciones.
  • Para depurar, coloca un else intermedio con un color llamativo o una imagen de aviso. Verás rápido qué rama está fallando.
  • Combina con propiedades personalizadas para exportar estados desde HTML (data-attributes) o desde otros selectores, y decidir valores sin JS.
  • Evita abusar de condicionales encadenados muy largos; si pierdes la lectura, es momento de extraer lógica a otro sitio.

Comparativa rápida con “lo de siempre”

Antes: base + @media/@supports con reescrituras; ahora: if() intercalado sin dividir la lógica. Menos CSS repartido, más cohesión local en la declaración. Gana el mantenimiento.

Antes: JS para alternar clases o estados; ahora: custom properties + style() para decidir propiedades. Con if() reduces la dependencia de código imperativo para variaciones visuales sencillas.

Notas de implementación y futuras combinaciones

El aterrizaje de if() coincide con otras líneas de evolución del lenguaje: consultas de estilo a la carta, posibles consultas de rango integradas en el futuro y la propuesta de @function para funciones personalizadas. Juntas, estas piezas dibujan un CSS más declarativo y componible.

Por ahora, céntrate en los tres tests disponibles (style(), media(), supports()) y en solidificar tu estrategia de fallbacks. Con Chrome 137 ya puedes empezar a prototipar y medir el impacto real en tu base de código.

La idea de “condicionales en CSS” dejó de ser un deseo para convertirse en una herramienta concreta que, con matices, ya se puede usar en entornos compatibles. Con if(), los estilos ganan lógica intercalada, menos repetición y mejor encapsulado, y encaja de maravilla con variables personalizadas, data-atributos y consultas de medios. Aunque su soporte todavía no es universal, adoptar patrones progresivos y preparar fallbacks te permitirá empezar a beneficiarte hoy de una forma de pensar el CSS más cercana a cómo diseñamos interfaces: tomando decisiones justo donde se aplican.