2 puntos por GN⁺ 2025-05-19 | 1 comentarios | Compartir por WhatsApp
  • Con la función contrast-color(), el navegador elige automáticamente texto negro o blanco según distintos colores de fondo, como en botones
  • Incluso en proyectos grandes, facilita mantener la legibilidad del texto y mejora la eficiencia de mantenimiento
  • Actualmente, en Safari Technology Preview se usa el algoritmo oficial de WCAG 2, aunque puede no coincidir con la percepción humana real
  • Durante el desarrollo de WCAG 3 se está debatiendo la adopción del algoritmo de nueva generación APCA, que promete una mejor evaluación del contraste de luminosidad
  • Además del simple contraste entre blanco y negro, en el futuro se espera la incorporación de más opciones de color y funciones de mejora de accesibilidad

Panorama general y contexto de la llegada de contrast-color()

  • En diseños donde se usan distintos colores de fondo en botones o componentes de interfaz, la legibilidad del color de la fuente (color del texto) es importante
  • Hasta ahora, los desarrolladores tenían que combinar manualmente cada color de fondo con su color de texto, pero en proyectos grandes esto aumentaba la complejidad de gestión y el riesgo de errores
  • Con la función CSS contrast-color(), el desarrollador solo necesita definir el color de fondo, y el navegador elige automáticamente entre texto negro o blanco el que tenga mayor contraste
  • Este enfoque mejora notablemente la eficiencia del mantenimiento y del trabajo de diseño
  • Se puede usar con una declaración simple como color: contrast-color(color);

Ejemplo de uso de contrast-color()

  • Al definir el color deseado en la variable del fondo del botón, el color del texto se selecciona automáticamente con contrast-color() como uno de los dos tonos contrastantes: negro o blanco
  • Como solo hay que gestionar un color a la vez, el mantenimiento se vuelve más sencillo cuando cambian las políticas de diseño o se agrega compatibilidad con modo oscuro/claro
button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
}
  • Si se aprovecha Relative Color Syntax, también se puede mantener de forma consistente el color de fondo y el color del texto en el estado hover

Consideraciones de accesibilidad y explicación del algoritmo

  • Usar contrast-color() no resuelve automáticamente todos los problemas de accesibilidad (Accessibility)
  • En algunos colores de fondo con brillo intermedio, puede ocurrir que ni el negro ni el blanco alcancen el nivel requerido
  • El algoritmo WCAG 2 que se usa actualmente en Safari Technology Preview es el estándar oficial de accesibilidad web
    • Este algoritmo elige en función de la relación de contraste, pero a veces produce resultados que no coinciden con el contraste de luminosidad percibido por el ojo humano
  • Por ejemplo, sobre un fondo azul #317CFF, mecánicamente se calcula que el negro tiene mayor contraste, pero en la práctica el blanco se lee mejor
  • Debido a estas críticas y solicitudes de mejora, en el estándar de accesibilidad de nueva generación (WCAG 3) se está discutiendo la adopción de APCA (Accessible Perceptual Contrast Algorithm)
  • APCA calcula el contraste de color teniendo en cuenta las características de la percepción humana, por lo que garantiza mejor la legibilidad real

Cómo ofrecer suficiente contraste en entornos reales

  • Con la media query @media (prefers-contrast: more) de CSS, se pueden aplicar estilos adicionales de alto contraste según las preferencias de accesibilidad del usuario
@media (prefers-contrast: more) {
  /* Definir estilos de mayor contraste */
}
  • Por ejemplo, si el color principal de una marca es un verde brillante como #2DAD4E, aunque contrast-color() en el futuro elija blanco, eso podría seguir sin ofrecer suficiente contraste para texto pequeño
  • Al aplicar el algoritmo APCA, se pueden consultar con más detalle los criterios mínimos de contraste necesarios según el tamaño y grosor de la fuente, lo que ayuda en decisiones de diseño reales
    • De hecho, para texto de 24px con peso 400, usar blanco es adecuado, pero para fuentes más delgadas o texto más pequeño se recomienda usar un color de fondo más oscuro
  • El equipo de diseño puede gestionar fácilmente con variables una paleta de colores adaptada a cada condición, considerando light/dark mode y preferencias como prefers-contrast
--button-color: #2DAD4E;

@media (prefers-contrast: more) {
  @media (prefers-color-scheme: light) {
    --button-color: #419543;
  }
  @media (prefers-color-scheme: dark) {
    --button-color: #77CA8B;
  }
}
button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
  font-size: 1.5rem;
  font-weight: 500;
}
  • En esencia, gracias a contrast-color(), basta con gestionar los colores tomando como base el color de fondo, y el navegador genera automáticamente el par de contraste para el texto

Más allá del negro y el blanco

  • La versión actual de contrast-color() solo elige entre dos opciones, blanco y negro, pero las versiones iniciales también podían seleccionar entre varios colores
  • El CSS Working Group priorizó primero una versión simple —que solo elige entre blanco y negro— por compatibilidad con cambios futuros de algoritmo, y también planea ampliaciones para el futuro, como opciones de color personalizadas o la posibilidad de definir un contraste mínimo deseado
  • Para necesidades simples, ya es suficientemente útil
  • Esta función puede aplicarse no solo al color de fondo, sino también a bordes y otros elementos visuales

Conclusión e información de referencia

  • Una vez que se refleje el estándar de accesibilidad de nueva generación, contrast-color() podrá cambiar de algoritmo para ofrecer una mejor selección automática del contraste
  • Hasta entonces, es especialmente útil cuando el color de fondo principal es claramente claro u oscuro
  • También puede aplicarse ampliamente a distintos elementos de UI, no solo al texto
  • Conviene seguir de cerca algoritmos modernos de accesibilidad como APCA (Accessible Perceptual Contrast Algorithm)

Material de referencia

  • En la documentación oficial de APCA y en APCA Contrast Calculator se pueden revisar distintos ejemplos y criterios de evaluación
  • CSSWG sigue avanzando en la discusión de estandarización de la función contrast-color
  • También es posible compartir opiniones y participar con retroalimentación en WebKit o en comunidades relacionadas

1 comentarios

 
GN⁺ 2025-05-19
Comentarios de Hacker News
  • Estoy desarrollando una herramienta para crear paletas en las que los pares de colores tengan una relación de contraste WCAG/APCA simple y predecible desde la etapa de diseño para resolver este problema. En https://www.inclusivecolors.com/ hay más funciones disponibles en escritorio. Un método consiste en crear muestras de color por niveles del 100 (color claro) al 900 (color oscuro) y ajustar la luminosidad para que, por ejemplo, un color de nivel 700 tenga un contraste claro con uno de nivel 100, y uno de nivel 800 con uno de nivel 200. Así puedes saber que combinaciones como red-700 vs gray-100 o green-800 vs yellow-200 ofrecerán un contraste claro sin tener que revisar la luminosidad. En el menú Contrast también se puede explorar qué tan más estricto es el algoritmo APCA frente a WCAG, especialmente qué tan exigente es con texto oscuro sobre fondos claros. Esa es la razón por la que no se debería usar WCAG para temas oscuros. En el menú Examples, si ves los casos de las paletas de Tailwind e IBM Carbon, cada nivel cambia la saturación y el matiz (Hue) de forma no lineal, así que elegir simplemente entre blanco o negro para obtener el mejor contraste es fácil, pero las paletas donde el branding importa son un problema más complejo que no se resuelve solo ajustando el brillo.

  • Hay una forma de hacer algo parecido usando lch

     --text: lch(from var(--bg) calc((49.44 - l) * infinity) 0 0);
    

    Fuente: https://til.jakelazaroff.com/css/…

    • LCH también está genial, pero OKLCH es aún mejor. https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl/… Ese artículo cambió por completo mi forma de pensar. Es una herramienta realmente increíble. Sorprendentemente, ninguno de mis amigos diseñadores conocía OKLCH. Este enfoque resuelve muchos problemas.

    • Es la primera vez que veo una función de CSS que puede recibir parámetros de esta manera, como con callbacks. Es un concepto realmente interesante. Me pregunto si habrá otras funciones además de lch con este estilo.

    • Lea Verou escribió un muy buen artículo sobre una solución alternativa similar. https://lea.verou.me/blog/2024/contrast-color/

  • Este artículo es una excelente visión general de las ventajas y desventajas de la selección automática de contraste. Si estás haciendo un sitio sencillo, este método da el contraste correcto de manera fácil y simple.
    Pero si necesitas cumplimiento de WCAG a gran escala, conviene evitarlo y tener una verdadera capa de tokens de formato semántico. Los tokens semánticos aceleran el desarrollo y pueden garantizar un contraste visualmente más agradable que un simple cambio entre negro y blanco. Lo bueno de una capa de tokens semánticos es que crear temas se vuelve muy fácil, así que tener tema claro/oscuro casi no tiene costo adicional. Si un color de marca no cumple con WCAG2, puedes crear un tema separado para WCAG2/APCA y así lograr cumplimiento mientras ofreces un mejor contraste.
    Yo llevo el stream de variables/tokens en Figma y también participé en la implementación del modo oscuro de Figma y Atlassian. Si alguien tiene preguntas sobre tokens, temas o colores de accesibilidad, con gusto respondo.

    • Me gustaría entender con más detalle qué son exactamente los tokens semánticos. Por este tipo de necesidades usé CSS-in-JS en un proyecto grande en el que trabajé, para cálculo relativo de color y colores de contraste. Espero que este tipo de tecnología se adopte ampliamente pronto.

    • Los últimos 2/3 son demasiado largos y suenan pretenciosos. En un sitio o app corporativa, depender de una función así es riesgoso porque el color resultante no es predecible. WebKit podría cambiar el resultado de color cuando corrija un bug.

  • Todavía me cuesta aceptar que el color de contraste se determine según el criterio de los fabricantes de navegadores, y que además no siempre sea correcto ni predecible. Me pregunto si se establecerá un criterio determinista para que todos los navegadores den el mismo resultado. Esta función en realidad se siente más como una herramienta de apoyo para el equipo de UX durante la etapa de diseño.

    • Según el artículo, se menciona que el estándar especifica claramente el método de cálculo.

    • La expresión "elige" me parece ambigua. En realidad, el algoritmo calcula el color.

    • Dejando de lado algunos errores o puntos confusos en la fórmula de ejemplo de APCA del enlace, funciona con 100% de precisión. Si quieres que sea perfectamente consistente, cuando ambos colores candidatos (tanto el más claro como el más oscuro) sean válidos al mismo tiempo, puedes usar la luminosidad del color de fondo (L*) y, por ejemplo, elegir el claro si L* es 60 o más. Así obtienes consistencia del 100%.

  • Sobre lo difícil que es vigilar por separado que en proyectos grandes un botón no termine con un color invisible, como texto negro sobre fondo oscuro, pensé que tal vez simplemente se podrían revisar todos los botones antes del lanzamiento. O también establecer y compartir con todo el equipo una regla previa de que jamás se use texto negro sobre botones oscuros. Me pareció interesante la diferencia entre contraste cognitivo y contraste matemático. Planeo aplicarlo al flujo de trabajo.

    • En la práctica sí es posible revisar todos los botones, pero haciéndolo así el período de pruebas de regresión previo al lanzamiento puede tomar semanas, o incluso meses. En proyectos grandes es fácil superar los miles de botones, y muchos solo aparecen con ciertas combinaciones de opciones o flujos de trabajo.

    • Si tomas APCA como referencia, puedes calcular contraste con base en percepción.

  • Tengo experiencia creando estilos de botones cuando los colores del sistema eran populares. Se veían bien, pero como no se podía saber cuál sería la relación de contraste, alguien hizo un cálculo con JavaScript usando getComputedStyle. Si el contraste era deficiente, se usaba un segundo color candidato, o cuando no había alternativa se reforzaba el contraste alrededor del texto con text-shadow. Ya olvidé cómo se calculaba, pero quizá baste con promediar los 3 valores RGB y compararlos. En azul el promedio sería más bajo, así que se podría dar prioridad al texto blanco.

  • Como mínimo, estaría bien que en temas light/dark recomendaran buenos colores para pseudoclases como active, focus, hover, link y visited. Material UI además suma estados como disabled, before y after.

  • Hace tiempo hice un tutorial en video sobre cómo elegir entre texto negro o blanco según el color de fondo. Mi método era simple: convertía el color a escala de grises y decidía cuál de los dos usar. Fue un trabajo divertido. No soy muy bueno haciendo videos.
    https://youtu.be/tUJvE4xfTgo?si=vFlegFA_7lzijfSR (ojo: está en portugués)

    • Curiosamente, en otro comentario presentaron justamente una fórmula de espacio de color que hace eso.
      https://news.ycombinator.com/item?id=44015990
      El video también se ve bien. El código parece bueno, pero como no sé portugués no puedo evaluar el contenido.
  • Si uno ya está eligiendo manualmente todo el esquema de color, me cuesta entender por qué elegir el color del texto de contraste en un botón sería más fácil que simplemente elegirlo desde el principio. Esta función parece útil solo en una situación muy extrema, donde el color de fondo se elige de forma arbitraria y variada, pero por alguna razón no puedes elegir el color del primer plano, es decir, el color del texto del botón. Los casos que realmente son problemáticos son textos sobre imágenes o fondos variados, donde siempre deben verse bien, y esta función no resuelve eso en absoluto. Por eso parece una función que solo ayudaría en situaciones limitadas, y aun así inventaron un verbo nuevo para ello, la función solo termina eligiendo entre blanco y negro, y además usa el peor algoritmo de contraste posible (WCAG 2). Increíble, la verdad.

    • Es una lástima descartar una herramienta solo porque nunca te ha tocado una situación donde usarla. Hay muchos sitios web en los que el usuario elige colores arbitrarios o se extraen colores de contenido subido. Los sitios que se preocupan por accesibilidad tienen que calcular contraste automáticamente en esos casos. Si aparece una función CSS integrada así, se vuelve mucho más fácil cubrir la accesibilidad básica. Claro, eso no limita en nada a los desarrolladores que quieran crear una experiencia más avanzada. Sería aún mejor si permitiera más personalización, como el paquete npm contrast-color, pero en el blog dicen que empezaron con un algoritmo que por ahora solo elige entre blanco y negro como primer paso, y que planean mejorarlo más adelante.
      Ejemplo: https://coolors.co/8fbfe0-7c77b9-1d8a99-0bc9cd-14fff7

    • Respecto a la crítica de que el algoritmo de selección de contraste no es bueno, dejan claro que sigue el algoritmo de WCAG 2 y que más adelante, cuando WCAG 3 se estandarice, podrán cambiar fácilmente a ese algoritmo.

  • Me pregunto si existe una alternativa en build time para aplicar algo así en SASS, Tailwind, etc. Parece que tomará tiempo para que esta función se adopte por completo, y también me preocupa si la implementación será igual en todas las plataformas.