Colores en CSS moderno: oklch(), color-mix() y más

El CSS moderno incorpora funciones de color que resuelven problemas antiguos del HEX y del HSL. Las más importantes son oklch(), que describe el color por luminosidad, croma y tono de forma perceptualmente uniforme, y color-mix(), que mezcla dos colores directamente en el navegador. Junto con la sintaxis de color relativo y light-dark(), te permiten generar escalas, estados hover y temas oscuros a partir de una sola variable.

Estas funciones forman parte de las especificaciones CSS Color Level 4 y 5. En esta guía verás la sintaxis de cada una con ejemplos reales, cuándo conviene usarla y, al final, un plan para migrar una paleta HEX a OKLCH sin romper nada.

La nueva sintaxis de rgb() y hsl()

El primer cambio es pequeño, pero aparece en todas partes: los valores ahora se separan con espacios y la transparencia va después de una barra. rgba() y hsla() siguen funcionando, pero ya no son necesarias.

/* sintaxis antigua */
color: rgba(255, 87, 51, 0.5);

/* sintaxis moderna */
color: rgb(255 87 51);
color: rgb(255 87 51 / 50%);
color: hsl(11 100% 60% / 0.8);

Todas estas líneas describen el mismo naranja, #FF5733, con o sin transparencia. El alfa acepta un número (0.5) o un porcentaje (50%). Todas las funciones nuevas de esta guía siguen el mismo patrón.

hwb(), lab() y lch()

hwb()

hwb() describe el color por tono, cantidad de blanco y cantidad de negro, parecido a mezclar pintura. Resulta intuitivo para aclarar u oscurecer a mano:

color: hwb(11 20% 0%);   /* = #FF5733 */
color: hwb(11 20% 30%);  /* mismo tono, más oscuro */

lab() y lch()

lab() y lch() usan el espacio CIELAB, diseñado para aproximarse a la percepción humana. En lab(), el primer valor es la luminosidad y los otros dos son ejes (verde–rojo y azul–amarillo). En lch(), esos ejes se convierten en croma (intensidad) y tono (un ángulo):

color: lab(61% 63.5 56);     /* ≈ #FF5733 */
color: lch(61% 84.7 41.4);   /* el mismo color en coordenadas polares */

CIELAB tiene una distorsión conocida: al variar la luminosidad de algunos azules, el tono se desplaza hacia el morado. OKLab nació para corregirlo.

oklab() y oklch(): color perceptualmente uniforme

OKLab, publicado por Björn Ottosson en 2020, mantiene la idea de Lab con tonos más estables. oklch() es su versión polar y el formato más práctico para el día a día:

color: oklch(0.68 0.21 33.7);      /* ≈ #FF5733 */
color: oklch(68% 0.21 33.7 / 50%); /* con transparencia */
color: oklab(0.68 0.175 0.116);    /* el mismo color en oklab */

Por qué importa

En HSL, una L del 50% no significa la misma claridad para todos los tonos. Compara el amarillo y el azul puros:

ColorHSLOKLCH
#FFFF00hsl(60 100% 50%)oklch(0.968 0.211 109.8)
#0000FFhsl(240 100% 50%)oklch(0.452 0.313 264.1)

Para HSL, los dos colores tienen la misma luminosidad. Para tu ojo, el amarillo es mucho más claro, y OKLCH lo refleja (0,968 frente a 0,452). En la práctica, esto significa que puedes crear escalas en las que el paso 500 de cada tono tenga un contraste parecido, y en las que cambiar solo el tono mantenga la legibilidad del texto.

:root {
  --blue-100: oklch(0.95 0.03 263);
  --blue-300: oklch(0.80 0.10 263);
  --blue-500: oklch(0.55 0.21 263);
  --blue-700: oklch(0.40 0.17 263);
  --blue-900: oklch(0.28 0.10 263);
}

Fíjate en que el croma baja en los extremos de la escala. Los colores muy claros o muy oscuros no admiten un croma alto dentro de sRGB. Cuando un valor queda fuera de la gama del monitor, el navegador lo ajusta al color visible más cercano.

color() y gama amplia (Display P3)

La mayoría de los móviles recientes, los Mac y muchos monitores muestran más colores que sRGB, sobre todo verdes y rojos saturados. La función color() te permite usar esos espacios:

.destacado { color: #FF0000; }

@media (color-gamut: p3) {
  .destacado { color: color(display-p3 1 0 0); }
}

En una pantalla P3, color(display-p3 1 0 0) es un rojo visiblemente más intenso que el rojo #FF0000. En pantallas sRGB, el navegador reduce el color a la gama disponible. oklch() también llega más allá de sRGB: basta con usar un croma más alto.

color-mix(): mezclas en el navegador

color-mix() recibe un espacio de color y dos colores con proporciones opcionales:

:root { --brand: #2563EB; }

.boton:hover {
  background: color-mix(in oklch, var(--brand) 85%, black);
}
.fondo-suave {
  background: color-mix(in oklch, var(--brand) 12%, white);
}
.borde {
  border-color: color-mix(in srgb, var(--brand), transparent 60%);
}

El espacio de mezcla cambia el resultado. En srgb, mezclar azul y amarillo pasa por un gris apagado en el centro; en oklch u oklab, la transición es más uniforme y conserva la viveza. La misma idea sirve para degradados con linear-gradient(in oklch, …), que puedes probar en el generador de degradados. El mezclador de colores te muestra el resultado de una mezcla antes de pasarla al código.

Sintaxis de color relativo

La sintaxis relativa parte de un color existente y modifica solo los canales que quieras. La palabra from descompone el color de origen en los canales del espacio elegido (l, c y h en OKLCH; r, g y b en RGB):

:root { --brand: #2563EB; }

.claro   { color: oklch(from var(--brand) calc(l + 0.1) c h); }
.oscuro  { color: oklch(from var(--brand) calc(l - 0.15) c h); }
.neutro  { color: oklch(from var(--brand) l 0.02 h); }
.cristal { background: rgb(from var(--brand) r g b / 20%); }
.opuesto { color: oklch(from var(--brand) l c calc(h + 180)); }

Así, todo un sistema de diseño puede derivar de una variable: cambia --brand y los tonos claros, oscuros, neutros y translúcidos se actualizan a la vez.

light-dark(), currentColor y accent-color

light-dark()

light-dark() recibe dos colores y usa el primero en el esquema claro y el segundo en el oscuro. Solo funciona si color-scheme está definido:

:root { color-scheme: light dark; }

body {
  background: light-dark(#FFFFFF, #121212);
  color: light-dark(#1F2937, #E5E7EB);
}

Con color-scheme: light dark, el navegador sigue la preferencia del sistema, y los controles nativos y las barras de desplazamiento también se adaptan.

currentColor

currentColor es el color de texto actual del elemento. Existe desde hace años, pero combina muy bien con las funciones nuevas: un icono SVG con fill: currentColor hereda el color del enlace, y color-mix(in oklch, currentColor 30%, transparent) crea un borde que acompaña al texto.

accent-color

accent-color aplica el color de tu marca a casillas de verificación, botones de opción, controles deslizantes y barras de progreso nativos, sin rehacer los componentes:

input, progress { accent-color: #2563EB; }

Soporte de navegadores y fallback con @supports

oklch(), oklab(), color-mix() y la sintaxis de color relativo ya tienen amplio soporte en los navegadores modernos y forman parte de Baseline. light-dark() es más reciente, y los navegadores antiguos que aún se usan pueden no reconocerla. Antes de decidir, consulta las tablas de compatibilidad actualizadas en MDN.

Hay dos formas sencillas de asegurar un fallback. La primera aprovecha la cascada: el navegador ignora la declaración que no entiende y se queda con la anterior.

.boton {
  background: #2563EB;
  background: oklch(0.546 0.215 262.9);
}

La segunda usa @supports, útil cuando todo un bloque depende de la función:

:root { --surface: #FFFFFF; }

@supports (color: light-dark(white, black)) {
  :root {
    color-scheme: light dark;
    --surface: light-dark(#FFFFFF, #121212);
  }
}

Cómo migrar una paleta HEX a OKLCH

La migración no tiene por qué hacerse de golpe. Un camino seguro:

  1. Haz inventario. Lista todos los HEX en uso y agrúpalos en tokens (--brand, --text, --surface…). Solo este paso ya elimina duplicados casi idénticos.
  2. Convierte sin cambiar nada. Usa el conversor de colores para obtener el equivalente OKLCH de cada HEX. Por ejemplo, #2563EB se convierte en oklch(0.546 0.215 262.9). Conserva tres decimales en L y C y uno en H para que no haya diferencias visibles.
  3. Mantén el HEX como fallback en la misma regla, como se muestra arriba, mientras tu público siga usando navegadores antiguos.
  4. Solo después, reorganiza la escala. Con los valores en OKLCH, alinea la luminosidad de cada paso (por ejemplo, todos los tonos 500 cerca de L 0,55) y reduce el croma en los extremos.
  5. Comprueba el contraste. La uniformidad perceptual no sustituye el cálculo de WCAG. Revisa los pares de texto y fondo en el verificador de contraste.

Cuidado: los valores OKLCH redondeados en exceso (como oklch(0.5 0.2 263)) pueden quedar un poco fuera de sRGB o dar otro color. Convierte con precisión y redondea solo al final.

Resumen y próximos pasos

Para practicar, convierte tus colores en el conversor, prueba mezclas en el mezclador y crea degradados en OKLCH con el generador de degradados. Si los formatos básicos aún no están claros, empieza por la guía de códigos de color, y para armar la paleta completa consulta cómo elegir la paleta de colores de una web.