wandres.dev
COLOR I · sRGB, gamma y el problema

Los límites de hex y de HSL

Qué es exactamente un hex y qué no puede expresar, por qué la L de HSL no es luminosidad percibida con los números que lo demuestran, y la diferencia entre notación y espacio de trabajo.

⏱ 17 min

Hex y HSL son las dos notaciones con las que se ha escrito casi todo el CSS de la historia, y las dos son perfectamente correctas para lo que se diseñaron: anotar un color que alguien ya ha elegido. El problema aparece cuando se usan para calcular —derivar una rampa, oscurecer un estado, comparar dos colores—, porque ninguna de las dos tiene una geometría que corresponda a la percepción, y hay un número concreto que lo demuestra sin lugar a discusión.

🎯 Al terminar esta lección sabrás
  • Explicar qué representa un hex y cuáles son sus dos límites duros.
  • Demostrar con luminancias que la L de HSL no es claridad percibida.
  • Anticipar el fallo de contraste de una paleta construida por niveles de L.
  • Distinguir el papel de una notación del de un espacio de trabajo.

Qué es exactamente un hex

#3b5bdb son tres bytes: los valores codificados de los canales rojo, verde y azul en el espacio sRGB. Nada más. Ni una definición de color absoluta ni una medida física: una terna de posiciones en tres escalas perceptuales comprimidas, con los primarios y el punto blanco que sRGB fijó en 1996.

De ahí salen sus dos límites, y son duros.

Ocho bits por canal están justo en el umbral. Los 16.7 millones de combinaciones suenan a exceso y no lo son cuando el gradiente es suave y grande. Con la codificación gamma repartiendo los códigos de forma aproximadamente uniforme en percepción, entre dos códigos consecutivos hay unos 0.4 puntos de claridad perceptual, y el umbral de detección del ojo para dos zonas amplias contiguas es del mismo orden. Por eso un degradado lento a pantalla completa bandea, y por eso existen los formatos de diez bits y el HDR. No es que ocho bits sean pocos por descuido: es que son exactamente los justos, y cualquier operación que consuma precisión —una mezcla, un desenfoque, una corrección de brillo— los deja cortos.

No puede expresar nada fuera de sRGB. Un panel moderno cubre Display P3, que abarca en torno a un 25% más de superficie en el diagrama de cromaticidad, sobre todo en rojos y verdes saturados. Ese territorio extra no tiene representación en hex: #ff0000 es el rojo más saturado que sRGB define, y hay rojos que tu pantalla puede emitir y que ninguna cadena de seis dígitos puede pedir. Para eso está color(display-p3 1 0 0) y las notaciones de espacios amplios.

Y un tercer límite que no es técnico pero cuesta más caro: un hex es opaco para un humano. Nadie sabe, mirando #4a7ba8 y #6b5f9e, cuál es más claro, cuál es más saturado o si comparten familia. Toda la información está ahí y ninguna está accesible.

La L de HSL no es luminosidad

HSL se inventó a finales de los setenta como una transformación geométrica del cubo RGB en un cilindro, con un objetivo explícito: que fuera barata de calcular y manejable con los controles de la época. Sus tres ejes se derivan de los valores codificados con unas cuantas operaciones de máximo, mínimo y división. No hay ningún modelo de percepción por dentro.

La demostración cabe en dos líneas. Estos dos colores tienen la misma L:

.azul     { background: hsl(240 100% 50%); }   /* #0000ff */
.amarillo { background: hsl(60 100% 50%); }    /* #ffff00 */

Y sus luminancias son:

azul     Y = 0.0722    claridad perceptual L* = 32.3
amarillo Y = 0.9278    claridad perceptual L* = 97.1

Trece veces más luz y tres veces más claridad percibida, con el mismo 50% escrito. La consecuencia práctica que lo hace inaceptable para un sistema de diseño está en el contraste:

contraste sobre blanco:  azul 8.59 a 1     amarillo 1.07 a 1
contraste sobre negro:   azul 2.44 a 1     amarillo 19.6 a 1

El mismo nivel de la paleta pasa holgadamente los requisitos de contraste en un tono y falla estrepitosamente en otro. Ese es el fallo que aparece en todos los sistemas de diseño construidos sobre niveles de L: la fila amarilla no cumple y nadie sabe por qué, porque en el código todos los tonos de esa fila son idénticos salvo el ángulo.

Los otros dos ejes tienen problemas equivalentes.

La S no es croma. Un 100% de saturación significa “lo más lejos del gris que permite el cubo en esa dirección”, y esa distancia es muy distinta según el tono. Dos colores con la misma S pueden tener intensidades cromáticas percibidas incomparables.

La H no es perceptualmente uniforme. Treinta grados de giro entre 240 y 270 apenas cambian el color; treinta grados entre 60 y 90 lo cambian por completo. Girar el tono un ángulo fijo para generar una paleta produce saltos irregulares, con familias enteras amontonadas y huecos en otras zonas.

Y un efecto más sutil que la gente nota sin poder nombrarlo: al bajar la L, el tono percibido se desplaza aunque el ángulo no cambie. Los azules oscurecidos tiran hacia el violeta. En el código el tono es exactamente el mismo; en la pantalla, no lo parece.

hwb() es otra transformación geométrica del mismo cubo, así que hereda los mismos problemas con otra parametrización.

Notación frente a espacio de trabajo

La conclusión no es que hex y HSL estén mal. Es que hay que separar dos usos que se confunden:

Como notación —anotar un color que ya está decidido— los dos son perfectamente válidos. Un hex es compacto, universal y sin ambigüedad; un HSL es legible y permite ajustar a mano con cierta intuición. Si tu sistema de diseño consiste en una lista de colores elegidos por una persona, escríbelos como quieras.

Como espacio de trabajo —derivar, interpolar, comparar, garantizar contraste— los dos son la herramienta equivocada, y por la misma razón: su geometría no corresponde a la percepción, así que operaciones iguales en el código producen efectos desiguales en la pantalla. Ese es el criterio, y no hace falta ninguna teoría más para aplicarlo.

Las tres operaciones que hay que sacar de ahí son siempre las mismas:

  • Generar una rampa de tonos a partir de un color base.
  • Derivar estados de una interfaz: hover, activo, deshabilitado.
  • Comparar o garantizar contraste entre dos colores.

Para las tres, el espacio de interpolación es un argumento que se elige, y la elección correcta es un espacio perceptual.

ℹ️
Lo que sí puedes seguir haciendo en hex

Escribir el valor. Guardarlo en un token. Copiarlo de la herramienta de diseño. Compararlo con el que te ha pasado el diseñador. Todo eso es notación y no tiene ningún problema. La línea se cruza en el momento en que aparece una operación aritmética sobre los componentes, y en CSS moderno esa operación siempre se puede pedir en otro espacio.

Cada sistema de diseño en HSL lleva dentro la geometría del cubo sRGB, y por eso no se podía arreglar con números

Piensa en lo que significa de verdad construir una paleta con niveles de L: has decidido que “el nivel 500” es una propiedad del color, y lo has definido con una coordenada de un espacio que es una deformación geométrica del cubo de sRGB, no una medida de nada percibible. El resultado es que el nivel 500 de tu amarillo y el 500 de tu azul no tienen absolutamente nada en común salvo un número en el CSS: ni claridad, ni contraste, ni presencia visual. Y aquí está la parte que convierte esto en una lección de ingeniería y no de color: el problema es inarreglable dentro del sistema. Los equipos que lo sufren lo intentan durante meses, ajustando los niveles de la fila amarilla a mano, añadiendo excepciones, documentando que “el amarillo funciona distinto”, y ninguna de esas correcciones se generaliza al siguiente color de marca que entre. No es una calibración que falte, es que la abstracción elegida —la coordenada L de HSL— no representa la propiedad que se quiere controlar, y ninguna cantidad de ajuste convierte una coordenada geométrica en una medida perceptual. La salida no es afinar los números sino cambiar el espacio, y reconocer ese tipo de callejón sin salida cuanto antes es una de las habilidades que más tiempo ahorran en cualquier disciplina: cuando llevas tres iteraciones corrigiendo casos particulares del mismo sistema, la hipótesis correcta ya no es que falten correcciones, es que la variable que estás controlando no es la que importa.