Módulo 4: Scales Color And Contrast
Contraste y WCAG
Descripción
La lección 4 terminó con una intuición: el contraste entre dos colores es la distancia entre sus claridades. Ahora la convertimos en un estándar. ¿Cuánta distancia hace falta para que un texto se lea? La respuesta no es "la que se vea bien" —es un número con un umbral fijo, definido por WCAG (Web Content Accessibility Guidelines), el estándar internacional de accesibilidad web—. El contraste se mide como un ratio que va de 1:1 (dos colores idénticos, ilegible) a 21:1 (negro puro sobre blanco puro, máxima legibilidad), y WCAG fija umbrales: AA (el mínimo legal en la práctica) pide 4.5:1 para texto normal, AAA (el nivel exigente) pide 7:1. En esta lección entiendes qué es el ratio, qué significan sus umbrales, por qué el texto grande tiene un umbral más laxo, y —lo más importante— por qué un sistema de diseño debe garantizar el contraste, no dejarlo al ojo.
Conexión con el módulo. Es el giro del módulo: de construir escalas de tamaño y color (L2–L4) a validar que esos colores son accesibles. Toma el eje de claridad que la lección 4 introdujo (los steps de una rampa) y le pone un estándar medible encima. Es la teoría que la lección 6 implementa (el algoritmo de contrastRatio) y la 7 aplica (elegir pares que pasen). En el proyecto (L8), garantizar el contraste del product-card es la mitad del entregable.
Una analogía: leer un letrero desde la calle
Imagina dos letreros en la fachada de una tienda. El primero tiene letras gris claro sobre fondo blanco. Desde la acera de enfrente, no se lee: los dos colores están tan cerca en claridad que las letras se disuelven en el fondo. El segundo tiene letras negras sobre fondo blanco. Se lee desde media cuadra. La diferencia no es el gusto del diseñador —el gris claro puede verse "más elegante"—; es que el negro y el blanco están lejos en claridad, y el gris claro y el blanco están cerca. La legibilidad es esa distancia, y es física, no estética.
El contraste de WCAG mide exactamente esa distancia, y lo hace pensando en quien más la necesita. Un letrero que tú lees con esfuerzo puede ser invisible para una persona con baja visión, para alguien mirando su teléfono bajo el sol, o para quien tiene una forma de daltonismo que aplana ciertos colores. El estándar no pregunta "¿lo lee un diseñador con vista perfecta en un monitor calibrado?"; pregunta "¿lo lee cualquiera, en condiciones reales?". Por eso fija un mínimo: por debajo de cierto ratio, hay personas que no pueden leer el texto, y eso deja de ser una preferencia para volverse una barrera.
Y hay un matiz que la analogía del letrero captura bien: las letras grandes se leen con menos contraste que las chicas. Un titular enorme en gris medio se lee aunque un párrafo en el mismo gris no se lea —porque el tamaño ayuda a distinguir la forma de la letra aun con poco contraste—. Por eso WCAG es más laxo con el texto grande: le pide 3:1 donde al normal le pide 4.5:1. No es un capricho; es que el tamaño compensa parte de la falta de contraste.
Guarda la imagen: el contraste es la distancia de claridad entre texto y fondo, medida pensando en quien más la necesita; WCAG fija cuánta hace falta como un número mínimo, más laxo para texto grande porque el tamaño compensa.
Ejemplo trabajado: el mismo ratio, dos umbrales
Antes de calcular ratios (eso es la lección 6), entendamos la tabla de umbrales, que es lo que un ratio dado tiene que superar. Los umbrales de WCAG 2.1 para contraste de texto son cuatro números, en una tabla de dos por dos —nivel (AA/AAA) por tamaño (normal/grande)—:
texto normal texto grande
AA 4.5:1 3:1
AAA 7:1 4.5:1
Modelamos la decisión con una función passesWCAG(ratio, { large }) que, dado un ratio ya calculado, devuelve el nivel máximo que ese ratio alcanza. Le pasamos unos ratios de ejemplo —los valores reales los produce contrastRatio en la lección 6— para ver cómo el mismo ratio cae en un nivel distinto según el tamaño del texto:
// L5 — los umbrales WCAG son NUMEROS, no opiniones. Dado un ratio, ¿que nivel pasa?
// Umbrales: texto normal -> AA 4.5, AAA 7 | texto grande -> AA 3, AAA 4.5.
// passesWCAG: dado un ratio y si el texto es grande, devuelve el nivel maximo que pasa.
function passesWCAG(ratio, { large } = {}) {
const aa = large ? 3 : 4.5;
const aaa = large ? 4.5 : 7;
if (ratio >= aaa) return 'AAA';
if (ratio >= aa) return 'AA';
return 'FAIL';
}
// unos ratios de ejemplo (los valores REALES los calcula contrastRatio en L6).
const samples = [21, 16.8, 4.6, 3.1, 2.8];
console.log('=== el mismo ratio, dos umbrales segun el tamano del texto ===\n');
console.log('ratio'.padEnd(8) + 'texto normal'.padEnd(15) + 'texto grande');
console.log('-'.repeat(35));
for (const r of samples) {
const normal = passesWCAG(r, { large: false });
const big = passesWCAG(r, { large: true });
console.log((r + ':1').padEnd(8) + normal.padEnd(15) + big);
}
Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:
=== el mismo ratio, dos umbrales segun el tamano del texto ===
ratio texto normal texto grande
-----------------------------------
21:1 AAA AAA
16.8:1 AAA AAA
4.6:1 AA AAA
3.1:1 FAIL AA
2.8:1 FAIL FAIL
Lee la tabla fila por fila; cada una enseña algo. 21:1 (negro sobre blanco) pasa todo, obviamente. 4.6:1 es el caso instructivo: como texto normal pasa solo AA (supera 4.5 pero no 7); como texto grande pasa AAA (supera con holgura el 4.5 que le exige el tamaño grande). El mismo par de colores, el mismo ratio, dos veredictos distintos —porque el umbral depende del tamaño—. 3.1:1 es aún más elocuente: falla para texto normal (no llega a 4.5) pero pasa AA para texto grande (supera el 3). Un color que es inaceptable en un párrafo puede ser aceptable en un titular. Y 2.8:1 falla en ambos: por debajo de 3 no hay tamaño que lo salve.
La lección práctica está en esas filas del medio. El contraste no es un sí/no absoluto: es un número comparado contra el umbral que corresponde al contexto (nivel objetivo y tamaño del texto). Por eso passesWCAG recibe { large }: sin saber el tamaño, no puedes decir si un ratio pasa. Un sistema de diseño que quiere garantizar accesibilidad necesita, entonces, dos cosas: calcular el ratio (lección 6) y saber a qué umbral compararlo (esta lección).
Una pregunta para fijar los umbrales: ¿qué define "texto grande" para WCAG? (Es texto de al menos 18pt (≈24px), o 14pt bold (≈18.66px) si va en negrita. Por eso un text-2xl (31.25px) de Mercado cuenta como grande y puede pasar con 3:1, mientras que un text-base (16px) es normal y necesita 4.5:1. El umbral que aplicas depende del token tipográfico de la lección 3 —las escalas del módulo se cruzan aquí—.)
Profundización: por qué el sistema debe garantizarlo, y qué mide (y qué no)
¿Por qué meter el contraste en el sistema y no dejarlo como un chequeo al final? Porque el contraste no es propiedad de un color: es propiedad de un par (texto sobre fondo). El azul de marca #2563eb no "es accesible" ni "es inaccesible" en abstracto; es accesible sobre blanco (5.17:1, pasa AA) e inaccesible sobre un azul claro. Eso significa que la accesibilidad se decide en cada combinación, y las combinaciones son justamente lo que un sistema controla: qué step de texto va sobre qué step de fondo. Si el sistema garantiza que los pares que ofrece pasan el umbral, la accesibilidad viene por construcción —nadie tiene que acordarse de chequearla pantalla por pantalla—. Si no, cada desarrollador improvisa combinaciones y algunas fallarán sin que nadie lo note hasta que un usuario no pueda leer.
Hay una razón de negocio, además de la ética. En muchos países el contraste mínimo (nivel AA) es una obligación legal —leyes de accesibilidad como la ADA en EE.UU., la EN 301 549 en Europa— y las demandas por sitios inaccesibles son reales y costosas. "Se veía bien" no es defensa; el estándar es un número, y o lo pasas o no. Un sistema que garantiza AA protege al producto tanto como al usuario.
Ahora, la honestidad sobre los límites de este número. El ratio de contraste de WCAG 2.x es una aproximación —buena y estándar, pero imperfecta—. Mide luminancia relativa (lección 6), que captura bien la legibilidad en la mayoría de los casos, pero tiene puntos ciegos conocidos: subestima problemas con ciertos colores muy saturados y no modela del todo cómo el ojo integra el texto fino. Por eso existe una propuesta más nueva, APCA (Advanced Perceptual Contrast Algorithm), candidata para WCAG 3, que modela la percepción con más fidelidad. Para este módulo —y para casi todo el trabajo real hoy— el estándar vigente y exigible es WCAG 2.1, y ese es el que implementamos. Menciono APCA para que sepas que el tema evoluciona; el algoritmo de la lección 6 es el que la ley y las herramientas usan hoy.
Y una frontera importante: el contraste de color que medimos aquí cubre la legibilidad texto-fondo, que es la base. La accesibilidad completa es mucho más —foco visible, orden de tabulación, roles ARIA, texto alternativo, no depender solo del color para comunicar (un error no puede señalarse únicamente con rojo, porque un daltónico no lo distingue)—. Todo eso vive en la guía de accesibilidad. Aquí garantizamos la pieza que sale de las escalas y paletas de este módulo: que los pares de color legibles, se leen.
El espectro del contraste (texto sobre fondo):
1:1 ─────────── 3:1 ─── 4.5:1 ─────── 7:1 ─────────────── 21:1
identico │ │ │ negro/
(invisible) │ │ │ blanco
│ │ └─ AAA normal / (grande ya paso)
│ └─ AA normal / AAA grande
└─ AA grande (minimo para titulares)
Por debajo de 3:1 no hay texto legible para todos, a ningun tamano.
Errores comunes
Creer que el contraste es una opinión y no un número. Qué pasa: en una discusión de diseño alguien dice "a mí ese gris me parece que se lee bien" y se zanja por gusto. Por qué pasa: el color se siente subjetivo, así que su legibilidad parece debatible. Cómo detectarlo: en tu proceso nadie corrió un cálculo; el veredicto salió de una impresión. Cómo corregirlo: el contraste es un número con un umbral fijo. "Me parece que se lee" no es un dato; 4.6:1, pasa AA sí lo es. La opinión estética decide entre los pares que pasan; no decide si un par pasa. Cuando alguien defienda un color por cómo se ve, mídelo —el número cierra la discusión, en un sentido o en el otro—.
Olvidar que el texto grande tiene un umbral más laxo. Qué pasa: se rechaza un color para un titular porque "no llega a 4.5", cuando el titular es grande y solo necesita 3:1. O al revés: se aprueba un color para un párrafo porque "pasa el 3 de texto grande", pero el párrafo es texto normal y necesita 4.5. Por qué pasa: se aplica un solo umbral a todo. Cómo detectarlo: tu chequeo no distingue el tamaño del texto; usa 4.5 (o 3) para todo. Cómo corregirlo: el umbral depende del tamaño —normal 4.5, grande 3 para AA—. Un ratio de 3.5:1 falla en un párrafo y pasa en un titular. Antes de dar un veredicto, mira qué token tipográfico usa el texto: text-2xl es grande, text-base es normal. passesWCAG te obliga a pasarle { large } justo para que no lo olvides.
Medir el contraste contra el color equivocado. Qué pasa: se mide el texto contra el fondo del body (blanco), pero el texto en realidad vive sobre una tarjeta de otro color (un muted gris, o un badge azul), y ese es el fondo real. Por qué pasa: uno mira el color de la página, no el del elemento inmediatamente detrás del texto. Cómo detectarlo: tu texto "pasa" en la herramienta pero se ve mal en pantalla —porque mediste contra un fondo que no es el que está debajo—. Cómo corregirlo: el fondo que cuenta es el que está directamente detrás del texto, no el de la página. Un texto gris sobre una tarjeta gris muted puede fallar aunque contra el blanco del body pasara. La lección 6 vuelve sobre esto con números: el mismo gris da 4.83:1 sobre blanco (pasa) y 1.07:1 sobre el azul primario (invisible). Siempre mide contra el fondo real.
Ejercicios
Ejercicio 1 — Aplica el umbral. Sin correr nada, di qué nivel (AAA / AA / FAIL) alcanza cada ratio, para el tamaño indicado:
- (a)
ratio = 5.0, texto normal - (b)
ratio = 5.0, texto grande - (c)
ratio = 3.2, texto normal - (d)
ratio = 3.2, texto grande
Ver solución
- (a) AA. Texto normal: supera 4.5 (AA) pero no llega a 7 (AAA). → AA.
- (b) AAA. Texto grande: los umbrales bajan a 3 (AA) y 4.5 (AAA); 5.0 supera 4.5. → AAA.
- (c) FAIL. Texto normal: 3.2 no llega a 4.5. → FAIL.
- (d) AA. Texto grande: 3.2 supera el 3 de AA pero no el 4.5 de AAA. → AA.
El mismo ratio (5.0 en a/b, 3.2 en c/d) da veredictos distintos según el tamaño. Sin el tamaño, no hay veredicto —esa es la lección—.
Ejercicio 2 — Predice la salida. Sin correr nada, di qué imprimiría el ejemplo trabajado si agregáramos el ratio 7.0 a la lista de samples ([21, 16.8, 7.0, 4.6, 3.1, 2.8]). ¿Qué fila produciría 7.0?
Ver solución
7:1 AAA AAA
7.0 como texto normal alcanza justo el umbral AAA (ratio >= 7 es verdadero para 7.0, porque es >=, no >). Como texto grande, supera de sobra el 4.5 de AAA. El detalle fino: el >= significa que exactamente 7.0 pasa AAA normal —los umbrales son inclusivos—. Si el ratio fuera 6.99, la fila daría AA en normal. Esa frontera exacta importa cuando un par cae justo en el límite.
Ejercicio 3 — El sistema y la garantía. Un compañero dice: "medimos el contraste de cada pantalla antes de lanzarla; no necesitamos meterlo en el sistema de diseño". Sin correr nada, argumenta por qué garantizarlo en el sistema es mejor. Da al menos dos razones.
Ver solución
Dos razones (bastan):
- El contraste es propiedad de un par, no de un color, y los pares los define el sistema. Chequear "cada pantalla" es chequear las combinaciones después de que alguien las eligió, y algunas malas ya se colaron. Si el sistema solo ofrece pares que pasan (este step de texto sobre este step de fondo), las malas combinaciones no se pueden construir —la accesibilidad viene por construcción, no por inspección—.
- La inspección manual no escala y se olvida. "Medir cada pantalla antes de lanzar" depende de que alguien se acuerde, tenga tiempo, y no se equivoque —y falla justo cuando hay prisa—. Una app crece a decenas de pantallas y cientos de cambios; el chequeo manual se salta uno y nadie lo nota hasta que un usuario no puede leer. El sistema lo garantiza una vez, para siempre.
Una tercera, si quieres: es una obligación legal (AA) en muchos mercados, y "lo revisamos a veces" no es una defensa; el sistema que lo garantiza protege al producto de un riesgo real.
Resumen y siguiente paso
En esta lección convertiste el contraste de intuición en estándar medible. El contraste es un ratio de 1:1 (idénticos) a 21:1 (negro/blanco), y WCAG fija umbrales: AA pide 4.5:1 (texto normal) y 3:1 (texto grande); AAA pide 7:1 y 4.5:1. Con el letrero leído desde la calle viste por qué es física y no gusto —la distancia de claridad, medida pensando en quien más la necesita— y por qué el texto grande afloja el umbral (el tamaño compensa). Y lo comprobaste ejecutando: el mismo ratio 4.6:1 da AA en texto normal y AAA en texto grande —sin el tamaño, no hay veredicto—. También viste por qué el sistema debe garantizarlo: el contraste es propiedad de un par, y los pares los controla el sistema.
Antes de avanzar deberías poder: recitar los cuatro umbrales (AA/AAA × normal/grande); explicar por qué el texto grande usa un umbral más bajo; y decir por qué medir contra el fondo real (no el del body) es crucial.
Sabes qué umbral tiene que superar un ratio; falta saber cómo se calcula el ratio. La lección 6 abre la caja: el algoritmo real de contrastRatio. Vas a implementarlo en cuatro pasos —parsear el hex a RGB, linealizar los canales (deshacer la corrección gamma de sRGB), calcular la luminancia relativa (con el verde pesando siete veces más que el azul), y sacar el ratio—, y verificarlo contra valores conocidos: #000000 sobre #ffffff debe dar exactamente 21:1. Es donde el número que aquí comparaste con umbrales, por fin, lo produces tú.
Recursos
- W3C, "Contrast (Minimum)" (WCAG 2.1, criterio 1.4.3) — w3.org/WAI/WCAG21/Understanding/contrast-minimum.html. La fuente oficial del nivel AA (4.5:1 / 3:1) y la definición de "texto grande". En inglés.
- W3C, "Contrast (Enhanced)" (WCAG 2.1, criterio 1.4.6) — w3.org/WAI/WCAG21/Understanding/contrast-enhanced.html. La fuente oficial del nivel AAA (7:1 / 4.5:1). En inglés.
- web.dev, "Color and contrast accessibility" — web.dev/articles/color-and-contrast-accessibility. Por qué el contraste importa y a quién afecta; el marco de la analogía del letrero. En inglés.
- MDN, "WCAG color contrast" — developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Perceivable/Color_contrast. El resumen de MDN de los umbrales y su razón; buen repaso antes de la lección 6. En inglés.