Portada de UrsaKit con dos muestras de color y los umbrales de contraste 4.5:1 y 3:1 de WCAG

El contraste es la parte de la accesibilidad que los diseñadores notan antes, porque el fallo se ve: texto gris sobre una tarjeta blanca que nadie mayor de 40 años consigue leer. WCAG convierte ese juicio en un número, y el número tiene una definición precisa en vez de una regla de dedo.

La fórmula es corta, pero se usa mal con facilidad: un selector de color muestra valores hexadecimales, no luminancia, y dos grises que parecen contiguos en una paleta pueden caer a lados opuestos de la línea de 4.5:1. Esta guía cubre la definición, los umbrales y las comprobaciones que atrapan lo que la gente pasa por alto.

01Qué mide la relación de contraste

WCAG define la relación de contraste como (L1 + 0.05) / (L2 + 0.05), donde L1 es la luminancia relativa del color más claro y L2 la del más oscuro. La relación va de 1:1 entre dos colores idénticos a 21:1 para negro sobre blanco.

La luminancia relativa es donde está el trabajo real. Para sRGB, WCAG la define como una suma ponderada de los tres canales:

L = 0.2126 * R + 0.7152 * G + 0.0722 * B

RsRGB = R8bit / 255
if RsRGB <= 0.04045 then R = RsRGB / 12.92
else R = ((RsRGB + 0.055) / 1.055) ^ 2.4

La misma linealización se aplica a G y B. El exponente 2.4 es lo que da importancia a los pesos: los valores sRGB en crudo están codificados con gamma, así que un gris medio no tiene la mitad de luz que el blanco. El verde pesa más, y por eso el amarillo alcanza 19,56:1 contra el negro mientras el azul sufre.

El término 0.05 no es un ajuste arbitrario. Mantiene finita la relación en colores muy oscuros y encaja con el comportamiento de la fórmula cerca del negro. Reimplementar esto a mano es correcto, pero usa el umbral 0.04045 actualizado: el texto antiguo de WCAG usaba 0.03928, y la nota de la especificación explica que se actualizó en mayo de 2021.

El contraste se mide entre dos colores renderizados.

WCAG indica que, cuando no se especifica color de fondo, se asume blanco, y que el antialiasing puede desactivarse para la medición. Las páginas reales componen degradados, imágenes y opacidad antes de dibujar el texto, así que mide los píxeles que ve el usuario.

02Los umbrales que debes cumplir

Tres criterios cubren casi todas las decisiones de contraste de una página normal:

CriterioNivelRequisito
1.4.3 Contrast (Minimum)AA4.5:1 texto normal, 3:1 texto grande
1.4.6 Contrast (Enhanced)AAA7:1 texto normal, 4.5:1 texto grande
1.4.11 Non-text ContrastAA3:1 en componentes e iconos necesarios

Los tres mantienen las mismas excepciones: texto que forma parte de un logotipo o nombre de marca, texto puramente decorativo, texto que nadie puede ver y texto dentro de un componente de interfaz inactivo. Esa última es más estrecha de lo que parece: un botón desactivado queda exento, una etiqueta normal sobre un panel claro no.

WCAG 2.2 añade un requisito de contraste fuera de la sección de color. El criterio 2.4.13 Focus Appearance, de nivel AAA, pide al menos 3:1 de contraste entre los mismos píxeles en estado enfocado y sin enfocar. Si tu anillo de foco es un resplandor sutil sobre un fondo parecido, tú lo verás y el número no.

03Ejemplos: un dígito hexadecimal decide

Estas relaciones se calcularon el 2026-10-04 con la fórmula WCAG anterior, usando Python 3. Son los pares que más se discuten:

Primer planoFondoRelaciónAA normalAA grande
#000000#ffffff21.00:1PasaPasa
#1f2937#ffffff14.68:1PasaPasa
#2563eb#ffffff5.17:1PasaPasa
#767676#ffffff4.54:1PasaPasa
#777777#ffffff4.48:1SuspendePasa
#3b82f6#ffffff3.68:1SuspendePasa
#9ca3af#ffffff2.54:1SuspendeSuspende

Dos filas merecen memorizarse. #767676 es el gris claro clásico que aún pasa AA sobre blanco, y #777777 es el vecino a un dígito hexadecimal que suspende. #3b82f6 es un azul de marca popular que pasa AA en texto grande y titulares, pero no en cuerpo de texto.

Veintiuno a uno es el techo, así que negro sobre blanco no es "más seguro" de una forma que compense otros pares. Cada combinación se puntúa por separado, y el umbral se aplica al texto tal como se renderiza, con su peso y su tamaño.

El mismo cálculo en Python, con la salida que imprime:

def lum(h):
    c = [int(h[i:i+2], 16) / 255 for i in (1, 3, 5)]
    f = lambda v: v / 12.92 if v <= 0.04045 else ((v + 0.055) / 1.055) ** 2.4
    r, g, b = map(f, c)
    return 0.2126 * r + 0.7152 * g + 0.0722 * b

def ratio(a, b):
    hi, lo = sorted([lum(a), lum(b)], reverse=True)
    return (hi + 0.05) / (lo + 0.05)

for c in ('#767676', '#777777'):
    print(c, round(ratio(c, '#ffffff'), 2))
#767676 4.54
#777777 4.48

04Comprobar un par en el navegador

El Comprobador de contraste de UrsaKit implementa la fórmula anterior y muestra todos los umbrales a la vez.

  1. Introduce o elige el Text colour en el primer campo. Acepta un valor hexadecimal de 3 o 6 dígitos, y el valor por defecto es #1f2937.
  2. Introduce el Background colour en el segundo campo. El valor por defecto es #ffffff.
  3. Pulsa Swap si quieres intercambiar los dos valores sin volver a escribirlos.
  4. Lee la Contrast ratio y después las cuatro comprobaciones: AA texto normal en 4.5:1, AA texto grande en 3:1, AAA texto normal en 7:1 y AAA texto grande en 4.5:1.
  5. Mira el panel de vista previa, donde el mismo par se muestra como titular y como texto de 16px.

La vista previa importa porque una relación no es lo mismo que legibilidad. Las tipografías finas y ligeras pueden verse peor de lo que sugiere el número, y la propia definición de WCAG señala que las fuentes con trazos muy finos son más difíciles de leer con poco contraste. La herramienta dice si el par cumple; la vista previa dice si se lee.

Cuando lo que arreglas es el tema completo y no un solo par, cambia el token en un playground de CSS y vuelve a comprobar el resultado renderizado. Probar la variable aislada se salta el caso compuesto, donde el fondo de una tarjeta se apoya sobre el fondo de la página y el texto sobre la tarjeta.

05Qué cuenta como texto grande

El texto grande tiene un umbral más permisivo porque los glifos mayores necesitan menos contraste para leerse. WCAG define el texto a gran escala como al menos 18 puntos, o 14 puntos si es negrita, o un tamaño que produzca un tamaño equivalente para fuentes chinas, japonesas y coreanas.

Esas unidades son puntos tipográficos, no píxeles CSS. Con la proporción CSS de 96 píxeles por pulgada, 18 puntos son 24 píxeles y 14 puntos unos 18,67 píxeles. Un titular de 20px en negrita cuenta como texto grande; el mismo estilo sin negrita, no.

La cláusula CJK es la que más se pasa por alto al traducir. Un titular chino de 16px que en un diseño solo latino sería "normal" puede tener un tamaño equivalente distinto, y el criterio deja espacio explícito para el tamaño que hace legibles los glifos CJK en lugar de forzar el valor en puntos.

Diseña con dos tokens, no con uno.

Un único gris apagado rara vez sirve para cuerpo de texto y para letra pequeña. Define un token que supere 4.5:1 para el cuerpo y otro más oscuro para los pies de foto, y comprueba ambos contra cada color de superficie de tu paleta.

06Contraste no textual: iconos, bordes y anillos de foco

El criterio 1.4.11 extiende la idea más allá de las palabras. La información visual necesaria para identificar un componente de interfaz o su estado, y las partes de gráficos imprescindibles para entender el contenido, necesitan 3:1 frente a los colores adyacentes.

En la práctica eso cubre el borde de un campo cuando el borde es lo único que indica dónde empieza, la diferencia entre encendido y apagado de un interruptor, el contorno de una línea de gráfico que aporta significado y el indicador de foco de un control navegable por teclado. No cubre decoración, ni componentes cuya apariencia viene entera del navegador y el autor no ha reestilizado.

El texto de placeholder es una víctima frecuente. Un placeholder gris claro que supera 3:1 se confunde a menudo con un aprobado, pero el placeholder es texto, y el texto sigue el 1.4.3. Si el placeholder es la única etiqueta de un campo, necesita los 4.5:1 completos frente al fondo del campo.

07Errores comunes

SíntomaCausaSolución
El gris se ve bien en el portátil y no se lee en el proyectorSe midió contra el fondo equivocadoMide contra la superficie más oscura donde pueda aparecer el texto
El estado hover baja de AASolo se comprobó el estado por defectoComprueba default, hover, focus, active y visited
El texto sobre una capa translúcida suspendeSe ignoró el alfa en el cálculoCompón el color y mide el resultado
En modo oscuro pasan los titulares y falla el cuerpoUn token reutilizado en dos tamañosDa al cuerpo su propio token con más margen
Los enlaces en línea desaparecen en el párrafoEl color carga todo el significadoCombina contraste con subrayado u otra señal no cromática
Las series de un gráfico no se distinguenLas líneas bajan de 3:1 frente al área de dibujoSube el contraste o añade etiquetas directas y tramas

08Fuentes

Preguntas frecuentes

¿Qué relación de contraste exige WCAG?

El nivel AA exige 4.5:1 en texto normal y 3:1 en texto grande. El nivel AAA sube a 7:1 y 4.5:1. Los elementos no textuales como iconos y bordes de campos necesitan 3:1 según el criterio 1.4.11.

¿Cómo se calcula la relación de contraste?

WCAG la define como (L1 + 0.05) / (L2 + 0.05), donde L1 es la luminancia relativa del color más claro y L2 la del más oscuro. La luminancia relativa es una suma ponderada de los canales sRGB linealizados: 0.2126 R + 0.7152 G + 0.0722 B.

¿Qué cuenta como texto grande en WCAG?

Al menos 18 puntos, o 14 puntos si es negrita, o un tamaño que produzca un tamaño equivalente para fuentes chinas, japonesas y coreanas. Con la proporción CSS de 96 píxeles por pulgada, 18 puntos son 24 píxeles y 14 puntos unos 18,67 píxeles.

¿Por qué #767676 pasa y #777777 no?

Sobre blanco, #767676 mide 4.54:1 y #777777 mide 4.48:1. El umbral AA es 4.5:1, así que un único paso hexadecimal decide el resultado. Por eso un gris se mide, no se estima a ojo.

¿Los logotipos y botones desactivados necesitan contraste?

No. WCAG exime el texto que forma parte de un logotipo o nombre de marca, el texto puramente decorativo, el texto que nadie puede ver y el texto dentro de un componente de interfaz inactivo. Todo lo que un usuario debe leer sí entra en el criterio.

¿Para qué sirve el contraste no textual de 3:1?

Se aplica a la información visual necesaria para identificar componentes de interfaz y sus estados, y a las partes de gráficos imprescindibles para entender el contenido. Los bordes de campos, los estados de un interruptor, los anillos de foco y las líneas de un gráfico son casos típicos.

¿La opacidad cuenta en el cálculo del contraste?

En la práctica sí. Un color translúcido se compone con lo que hay detrás, y esos colores compuestos son los que ve el usuario. Mide el par final renderizado, no el valor alfa declarado.

Volver a los artículos

Artículos relacionados

Herramientas relacionadas