CSS siempre ha tenido condiciones, pero en bloques aparte: un
@media para el móvil, un @supports para lo que el navegador no
entiende, una clase modificadora para cada variante. La nueva función if()
mete la condición dentro del valor de la propiedad, en una sola línea. En
este post vemos cómo se escribe, los tres tipos de condición que acepta, un caso práctico
con botones y, muy importante, qué hacer en los navegadores que todavía no la entienden.
Explicación
Imagina que das instrucciones a alguien para preparar un café. La forma de siempre en CSS es escribir dos recetas completas: «receta para cuando hay leche» y «receta para cuando no hay». Si las dos son casi iguales, repites casi todo.
Con if() escribes una sola receta y, en el paso que cambia, dices: «añade leche
si hay; si no, un poco más de agua». La condición está justo donde importa, y el
resto no se repite.
En código, lo de siempre es esto:
.card {
padding: 1rem;
}
@media (width >= 768px) {
.card {
padding: 2rem;
}
}
Y con if(), esto:
.card {
padding: if(media(width >= 768px): 2rem; else: 1rem);
}
El mismo resultado, sin repetir el selector y con las dos opciones una al lado de la otra.
La sintaxis
if() recibe una lista de ramas separadas por punto y coma. Cada
rama es una condición, dos puntos y el valor que se usa si se cumple:
propiedad: if(
condición-1: valor-1;
condición-2: valor-2;
else: valor-por-defecto
);
- Las condiciones se comprueban en orden, y gana la primera que se
cumple. Como un
if / else if / elsede cualquier lenguaje. elsesiempre se cumple, así que va al final, como último recurso.- Si ninguna rama se cumple y no hay
else, la propiedad vuelve a su valor inicial (o al heredado), no a lo que hubieras escrito antes. Pon siempre unelse. - Sin espacio entre
ify el paréntesis.if (…)invalida la declaración entera. Es el error más fácil de cometer y el más difícil de ver.
Los tres tipos de condición
media(): según la pantalla
Acepta las mismas características que un @media: anchos, orientación, tipo
de puntero, preferencias del usuario…
.title {
font-size: if(media(width >= 1024px): 3rem; media(width >= 768px): 2.25rem; else: 1.75rem);
}
/* con ratón, botones compactos; con el dedo, más grandes */
.icon-button {
width: if(media(any-pointer: fine): 30px; else: 44px);
}
.hero {
animation: if(media(prefers-reduced-motion: reduce): none; else: fade-in 0.4s ease);
}
Fíjate en el primero: tres tamaños en una línea. Para una tipografía que crece poco a poco,
sin saltos, sigue siendo mejor
clamp(); if() es para cuando
quieres saltos concretos.
Lo que no acepta son los tipos de medio, como print o
screen: if(media(print): …) no se cumple nunca, ni siquiera al
imprimir. Para los estilos de impresión, sigue usando @media print.
supports(): según lo que entiende el navegador
Igual que @supports: usas algo moderno si el navegador lo entiende y otra cosa
si no.
.badge {
color: if(supports(color: oklch(70% 0.15 150)): oklch(70% 0.15 150); else: #3fa86b);
}
Con un matiz importante: solo los navegadores que entienden if() llegan a
evaluar el supports(), y todos ellos ya entienden oklch(). Así
que, hoy, este ejemplo no protege a nadie. supports() dentro de
if() tiene sentido para funcionalidades más nuevas que el propio
if(). Para todo lo demás, sigue usando @supports o un valor de
respaldo en la línea anterior (lo vemos más abajo).
style(): según una variable CSS
Esta es la novedad de verdad, la que no tenía equivalente sencillo. style()
comprueba el valor de una variable CSS (una custom property) del propio
elemento:
.button {
background: if(
style(--variant: danger): #e5484d;
style(--variant: success): #30a46c;
else: #3e63dd
);
}
Ahora el color depende de cuánto valga --variant. Y esa variable se puede
poner en el CSS, en el HTML o desde JavaScript. Hoy solo funciona con variables, no con
propiedades normales: style(display: flex) no se cumple aunque el elemento
sea flex.
Admite también and, or y not, en las tres
condiciones:
.panel {
border-color: if(
style((--variant: danger) and (--emphasis: high)): #b42318;
not style(--variant: danger): transparent;
else: #e5484d
);
}
Caso práctico: variantes de un botón
Un botón con tres variantes (normal, peligro y éxito) y dos tamaños. A la antigua, con modificadores de BEM, sería así:
.button {
padding: 0.5rem 1rem;
background: #3e63dd;
color: #fff;
}
.button--danger {
background: #e5484d;
}
.button--success {
background: #30a46c;
}
.button--large {
padding: 0.75rem 1.5rem;
font-size: 1.125rem;
}
.button:hover {
background: #3451b2;
}
.button--danger:hover {
background: #cd2b31;
}
.button--success:hover {
background: #2b9a66;
}
Funciona, pero cada variante se reparte por varias reglas, y el :hover tiene
que repetirlas todas. Además, en el orden correcto: .button:hover tiene que ir
antes que los de las variantes, porque pesan lo mismo y gana el último; si lo pones al
final, todos los botones se vuelven azules al pasar el ratón. Con if(), cada
propiedad lleva sus casos dentro:
.button {
--variant: default;
--size: medium;
padding: if(style(--size: large): 0.75rem 1.5rem; else: 0.5rem 1rem);
font-size: if(style(--size: large): 1.125rem; else: 1rem);
color: #fff;
background: if(
style(--variant: danger): #e5484d;
style(--variant: success): #30a46c;
else: #3e63dd
);
}
.button:hover {
background: if(
style(--variant: danger): #cd2b31;
style(--variant: success): #2b9a66;
else: #3451b2
);
}
Y en el HTML eliges la variante con la variable:
<button class="button">Guardar</button>
<button class="button" style="--variant: danger">Borrar</button>
<button class="button" style="--variant: success; --size: large">Publicar</button>
Si no te gusta poner style en el HTML, puedes seguir usando clases y que cada
una solo cambie la variable: .button--danger { --variant: danger; }. La
diferencia es que la lógica de los colores queda en un único sitio, en
.button, y las clases solo eligen.
Donde más brilla es con JavaScript. Cambiar el estado es tocar una variable, sin añadir ni quitar clases:
saveButton.style.setProperty('--variant', 'success');
Los «if» que ya tenías
if() no viene a sustituir nada: se suma a otras formas de poner condiciones que
ya existen y que siguen siendo la herramienta correcta en muchos casos.
@media: cuando cambian muchas propiedades a la vez según la pantalla. Si el menú pasa de horizontal a hamburguesa, mejor un bloque que diezif().@supports: lo mismo, cuando lo que cambia con el soporte es un diseño entero (por ejemplo, un layout consubgrid).@container: cuando la condición depende del tamaño de la caja que contiene al elemento, no de la pantalla. Una tarjeta que se ve distinta en la barra lateral y en el contenido principal.:has(): una condición sobre el HTML. «Si esta tarjeta tiene una imagen, ponla en dos columnas»:.card:has(img) { grid-template-columns: 1fr 2fr; }.
| Quiero que cambie según… | Uso |
|---|---|
| La pantalla, una o dos propiedades | if(media(…)) |
| La pantalla, medio diseño | @media |
| El tamaño de la caja que lo contiene | @container |
| Lo que tiene dentro en el HTML | :has() |
| Una variable (variante, estado, tema) | if(style(…)) |
| Si el navegador entiende algo | @supports (o if(supports(…)) para lo más nuevo) |
Soporte: cómo usarlo sin romper nada
Aquí está la letra pequeña. En el momento de escribir esto, if() funciona en
Chrome y Edge desde la versión 137 (y en Opera y demás navegadores basados
en Chromium), pero todavía no en Firefox ni en Safari. Antes de usarlo en
un proyecto, mira la tabla de compatibilidad actualizada en
MDN.
La buena noticia es que no hace falta esperar. Cuando un navegador no entiende una declaración, la ignora y se queda con la anterior. Así que basta con poner antes un valor de respaldo:
.card {
padding: 1rem; /* para los navegadores que no entienden if() */
padding: if(media(width >= 768px): 2rem; else: 1rem);
}
Firefox y Safari usan 1rem y Chrome usa el if(). Con
media() es fácil: el respaldo es el valor de móvil y en pantallas grandes
esos navegadores se quedan con él. Si eso no te vale, de momento sigue con el
@media de siempre.
Con style() hay que pensarlo más, porque no tiene un equivalente sencillo: el
respaldo será la variante por defecto. En el ejemplo del botón, en Firefox todos los
botones saldrían azules. Si eso es aceptable (una mejora visual, no algo que el usuario
necesita para entender la página), adelante. Si un botón de «Borrar» tiene que
verse rojo, de momento mantén también la clase .button--danger con su color.
Lo que if() no hace
- No sirve para bloques enteros. Decide el valor de una
propiedad. Si cambian ocho, son ocho
if(): ahí gana@media. style()no mira propiedades normales. Solo variables CSS: no puedes preguntar «si este elemento esdisplay: flex».- No mira al padre.
style()dentro deif()comprueba la variable del propio elemento. Como las variables se heredan, si la pones en el padre también la ve el hijo, pero para preguntar por el tamaño del contenedor sigues necesitando@container. - No sustituye a la lógica de verdad. Si una decisión depende de datos (si el usuario es administrador, si el pedido está pagado), se toma en el servidor o en JavaScript, y CSS solo recibe el resultado en forma de clase o de variable.
El resumen
| Necesito… | Uso |
|---|---|
| Un valor según el ancho de pantalla | if(media(width >= 768px): …; else: …) |
Un valor según el soporte de algo más nuevo que if() |
if(supports(…): …; else: …) |
| Variantes con una variable | if(style(--variant: danger): …; else: …) |
| Combinar condiciones | and, or, not |
| No romper Firefox y Safari | Un valor normal en la línea anterior |
| Cambiar medio diseño | @media, @container o :has() |
Y ya está
if() pone la condición donde importa: en el valor que cambia, no en un bloque
aparte que repite el selector. Para una o dos propiedades que cambian según la pantalla, y
sobre todo para las variantes con style(), deja el CSS mucho más corto y
fácil de seguir. Úsalo ya con un valor de respaldo delante, y cuando llegue a Firefox y
Safari solo tendrás que borrar una línea.