Cómo calcular el contraste de color según WCAG en JavaScript
La accesibilidad web exige que el texto sea legible sobre su fondo. WCAG define umbrales de contraste calculados con una fórmula que tiene en cuenta cómo el ojo humano percibe la luz.

Si alguna vez has intentado leer texto gris claro sobre fondo blanco y has tenido que entrecerrar los ojos, ya sabes de qué va este artículo. La legibilidad de un texto depende del contraste entre su color y el del fondo, y las Web Content Accessibility Guidelines (WCAG) han formalizado cuánto contraste es suficiente —y cuánto es excelente.
Los dos niveles: AA y AAA
WCAG 2.1 define dos umbrales para el contraste de color:
- Nivel AA (mínimo exigible): relación de contraste de 4,5:1 para texto normal y de 3:1 para texto grande (18 pt o 14 pt en negrita).
- Nivel AAA (óptimo): relación de 7:1 para texto normal y de 4,5:1 para texto grande.
El negro puro sobre blanco puro tiene una relación de 21:1 —el máximo posible. Un gris medio como #767676 sobre blanco da aproximadamente 4,48:1, que falla AA por los pelos.
La relación de contraste
La fórmula es sencilla. Dados dos colores, calculas la luminancia relativa de cada uno y aplicas esto:
ratio = (L_clara + 0,05) / (L_oscura + 0,05)
Donde L_clara es la luminancia del color más claro y L_oscura la del más oscuro. El 0,05 que se suma a ambos existe para que el negro puro no haga explotar el denominador a cero.
La luminancia relativa
Aquí está la parte no obvia. La luminancia relativa no es simplemente el promedio de los canales RGB: el ojo humano es mucho más sensible al verde que al azul, y la pantalla tampoco emite luz de forma lineal.
La fórmula de WCAG pondera los canales así:
L = 0,2126 × R_lin + 0,7152 × G_lin + 0,0722 × B_lin
Los coeficientes reflejan la sensibilidad del ojo: el verde domina, el rojo contribuye algo, el azul apenas pesa.
La linealización gamma
El punto más delicado: los valores RGB que usamos en CSS (#ff0000, rgb(255, 0, 0)) están codificados en el espacio de color sRGB, que aplica una corrección gamma para adaptarse a cómo funcionaban los monitores CRT. Antes de calcular la luminancia hay que invertir esa corrección y obtener valores lineales.
La fórmula tiene dos tramos porque la curva gamma tiene una parte lineal al inicio para evitar ruido en las sombras:
function linearizar(canal) {
var v = canal / 255;
return v <= 0.04045
? v / 12.92
: Math.pow((v + 0.055) / 1.055, 2.4);
}
Si te saltas este paso y usas los valores sRGB directamente, los contrastes calculados serán incorrectos —sobre todo en colores oscuros.
La luminancia en JavaScript
Con la linealización lista, calcular la luminancia es directo:
function luminancia(hex) {
var r = parseInt(hex.slice(1, 3), 16);
var g = parseInt(hex.slice(3, 5), 16);
var b = parseInt(hex.slice(5, 7), 16);
return 0.2126 * linearizar(r)
+ 0.7152 * linearizar(g)
+ 0.0722 * linearizar(b);
}
El ratio final
function ratioContraste(hex1, hex2) {
var l1 = luminancia(hex1);
var l2 = luminancia(hex2);
var lighter = Math.max(l1, l2);
var darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
No importa el orden en que pases los colores: la función siempre identifica cuál es el más claro.
Comprobación de los umbrales
Con el ratio calculado, evaluar los criterios WCAG es trivial:
var ratio = ratioContraste('#595959', '#ffffff'); // → ~7.0
var aaTextoNormal = ratio >= 4.5; // true
var aaTextoGrande = ratio >= 3; // true
var aaaTextoNormal = ratio >= 7; // true (justo en el límite)
var aaaTextoGrande = ratio >= 4.5; // true
Conectar con un formulario HTML
La parte visual es conectar los <input type="color"> y los campos de texto hexadecimal con la función de render. El truco habitual es mantener sincronizados ambos: cuando el usuario mueve el selector de color nativo se actualiza el hex; cuando escribe un hex válido se actualiza el selector.
nativeInput.addEventListener('input', function () {
hexInput.value = nativeInput.value.toUpperCase();
render();
});
hexInput.addEventListener('input', function () {
if (/^#[0-9a-fA-F]{6}$/.test(hexInput.value)) {
nativeInput.value = hexInput.value;
render();
}
});
Y la función render actualiza la vista previa con element.style.color y element.style.background, y marca cada criterio como superado o fallido.
A continuación puedes probar la herramienta completa.
Contraste de color WCAG
Elige el color del texto y el del fondo para comprobar si superan los umbrales de accesibilidad WCAG 2.1.
Aa — Texto de muestra
Texto a tamaño normal · Comprobador de contraste WCAG · paigar.eu


