Accesibilidad
Contraste de texto
Cómo calcula WCAG 2.2 el contraste, qué piden los criterios 1.4.3 (AA) y 1.4.6 (AAA), por qué se comprueba cada par de tokens en cada modo y cómo cumple DesignToken101.
Última revisión:
El contraste entre un texto y su fondo es el criterio de accesibilidad que más depende de los tokens. En los módulos anteriores viste que el contraste decide a qué paso apunta cada semántico. En esta lección verás cómo se calcula, qué umbral pide cada nivel y cómo se comprueba un sistema entero, par a par.
En esta página
- La fórmula
- Los umbrales
- Un par, no un color
- Cómo se comprueba un par
- Texto grande
- Cómo cumple DesignToken101
- Fondos con transparencia
- En Figma
- Lo que te llevas
La fórmula
WCAG 2.2 calcula el contraste en dos pasos. La técnica G18 los describe (G18):
- La luminancia relativa de cada color (L): cuánta luz refleja, de 0 (negro) a 1 (blanco). Se calcula sobre los valores sRGB del color, convertidos a valores lineales:
L = 0,2126 × R + 0,7152 × G + 0,0722 × B. El verde pesa mucho más que el azul porque el ojo es más sensible a él. - La razón de contraste entre los dos colores:
(L1 + 0,05) / (L2 + 0,05), donde L1 es la luminancia del color más claro y L2 la del más oscuro.
El resultado va de 1:1 (dos colores iguales) a 21:1 (negro sobre blanco).
Lo que dice la guía de 1.4.3 sobre cómo usarla (Understanding 1.4.3):
- No se redondea. 4,499:1 no llega a 4,5:1.
- Se mide con los colores que define el CSS, no con los píxeles que pinta la pantalla. El suavizado de los bordes de las letras no cuenta.
En Espacio de color viste la consecuencia para DesignToken101: el contraste se calcula sobre el hex guardado, que es el color que llega al CSS.
Los umbrales
| Criterio | Nivel | Texto normal | Texto grande |
|---|---|---|---|
| 1.4.3 Contrast (Minimum) | AA | 4,5:1 | 3:1 |
| 1.4.6 Contrast (Enhanced) | AAA | 7:1 | 4,5:1 |
Fuentes: Understanding 1.4.3 y Understanding 1.4.6.
El 4,5:1 no es arbitrario: compensa la pérdida de sensibilidad al contraste de una persona con una agudeza visual de 20/40, la habitual hacia los 80 años. El 7:1 está pensado para una agudeza de 20/80 (mismas fuentes).
Los dos criterios eximen el texto decorativo, el de componentes inactivos, el texto invisible, el que forma parte de una imagen con otro contenido importante y los logotipos. Por eso el logotipo de DesignToken101 queda fuera de la comprobación.
Un par, no un color
Un color no tiene contraste: lo tiene un par de colores. Por eso en DesignToken101 los primitivos no tienen una cifra de contraste propia. La tienen los pares de semánticos que se usan juntos: color/text/neutral/default sobre color/background/neutral/default, color/text/on-accent sobre color/background/accent/strong/default…
Y cada par se comprueba en cada modo. El mismo par de tokens tiene un hex distinto en Light y en Dark, así que tiene dos cifras. Un sistema con dos modos de color tiene el doble de comprobaciones que uno con un modo.
Cómo se comprueba un par
- Elegir el par, Token de texto y fondo donde se usa
- Leer el hex, El de cada modo, sin redondear
- Calcular la razón, Fórmula de WCAG 2.2
- Comparar con el umbral, 4,5:1 en el nivel AA
- Decidir, Vale, o se cambia un alias
- Elegir el par. El token de texto y cada fondo sobre el que aparece. Si un texto se usa sobre dos fondos, son dos pares.
- Leer el hex. El de la exportación de cada modo: es el
hexque copiaste en el ejercicio del módulo 6. - Calcular la razón con la fórmula de arriba, sin redondear.
- Comparar con el umbral del nivel que has elegido.
- Decidir. Si no llega, cambias el alias del texto o del fondo a otro paso de la escala, y vuelves a comprobar el par en los dos modos. Es lo que viste en El contraste decide el paso.
Texto grande
Para WCAG, texto grande es el de 18 puntos o más, o el de 14 puntos en negrita. En CSS equivale a unos 24 px, o unos 18,5 px en negrita (Understanding 1.4.3). Ese texto puede bajar a 3:1 en el nivel AA.
Recomendación
DesignToken101 no usa la excepción de texto grande: todos sus pares de texto llegan a 4,5:1. El motivo es de tokens. Un token de color no sabe a qué tamaño se va a usar, y en DesignToken101 los tamaños de los títulos cambian con el modo de Layout: heading/2 mide 32 px en Desktop y 24 px en Mobile (Crear los modos en Figma). Si un par de color dependiera del tamaño, habría que comprobarlo también en cada modo de Layout. Con 4,5:1 para todo, cualquier texto vale con cualquier color de texto.
Cómo cumple DesignToken101
tools/semantic.py calcula cada par de la especificación en Light y en Dark (comprobado el 2026-10-06). Todos los pares de texto llegan a 4,5:1, así que DesignToken101 cumple 1.4.3 (AA). Estos son los pares que no llegan a 7:1, el umbral de 1.4.6 (AAA), en algún modo, ordenados de menos a más margen en Light:
| Texto | Fondo | Light | Dark | 7:1 (AAA) |
|---|---|---|---|---|
text/warning/default | background/warning/subtle | 4,77 | 10,32 | En Dark |
text/success/default | background/success/subtle | 4,85 | 10,60 | En Dark |
text/accent/default | background/accent/subtle | 4,85 | 8,30 | En Dark |
text/accent/default | background/neutral/subtle | 4,86 | 9,83 | En Dark |
text/accent/default | background/neutral/default | 5,08 | 10,92 | En Dark |
text/danger/default (aún sin uso) | background/danger/subtle | 5,75 | 8,49 | En Dark |
text/on-accent | background/accent/strong/active | 5,93 | 5,93 | No |
text/info/default | background/info/subtle | 5,99 | 8,10 | En Dark |
text/accent/hover | background/neutral/hover | 6,62 | 10,58 | En Dark |
text/accent/hover | background/accent/subtle | 6,90 | 10,60 | En Dark |
text/neutral/subtle | background/neutral/subtle | 7,40 | 6,89 | En Light |
Los demás pares llegan a 7:1 en los dos modos. Entre ellos están el texto principal y el secundario sobre la página: text/neutral/default sobre background/neutral/default da 17,79 en Light y 18,89 en Dark, y text/neutral/subtle, 7,74 y 7,65. Los colores de acento y de mensaje en Light no llegan: sus pasos se eligieron para llegar a 4,5:1, que es el objetivo. Light es el modo que más se aleja de AAA.
Hay un par que no llega a 4,5:1: text/neutral/subtle sobre background/neutral/active en Dark, con 3,99:1. No se usa nunca, por una regla de Estados: en hover y active, el texto de los controles neutros pasa a text/neutral/default. Una regla de uso también es una forma de cumplir, si está escrita.
Fondos con transparencia
Un fondo translúcido no tiene un color propio: su color final depende de lo que pase por debajo. Por eso, en el ejercicio del módulo 6, anotaste el alpha de esos fondos.
Para comprobar un par sobre un fondo translúcido, calcula el peor caso: el fondo mezclado con el color más claro y con el más oscuro que puedan quedar debajo. En DesignToken101, la cabecera usa background/neutral/translucent (blanco al 96 % en Light, neutral/950 al 90 % en Dark). Lo que va encima es el logotipo, exento, el icono del botón de menú y el anillo de foco, que no son texto: lo verás en la lección siguiente, con el caso que llevó a subir Light al 96 %.
En Figma
Figma tiene un comprobador de contraste en el selector de color. Lo que dice su ayuda (Figma: Update fills using the color picker):
- Se abre con Check color contrast, en el selector de color de la capa.
- El color de la capa seleccionada es siempre el primer plano.
- Muestra la razón de contraste, de 1:1 a 21:1, y si cumple los niveles AA o AAA de WCAG.
- Puedes elegir el tipo de contenido: automático (según la capa seleccionada), texto grande, texto normal o gráficos. El nivel AAA está disponible para el texto, no para los gráficos. WCAG 2.2 tampoco tiene un criterio AAA para el contraste de lo que no es texto.
Recomendación
El comprobador de Figma te dice si cumple lo que ves en ese momento: un par, en el modo que tiene aplicado el marco. Úsalo mientras diseñas. Para comprobar el sistema, trabaja con la tabla de pares y el hex de cada modo: así ningún par ni ningún modo se queda sin revisar.
tools/semantic.py guarda los alias de cada semántico en Light y en Dark y una lista de pares, cada uno con su umbral y su criterio:
# token: (light, dark)
T = {
'color/background/neutral/default': ('white', 'neutral/950'),
'color/text/neutral/default': ('neutral/900', 'neutral/50'),
# …
}
# (primer plano, fondo, umbral, criterio, uso, referencia)
P = [
('color/text/neutral/default', 'color/background/neutral/default', 4.5, '1.4.3', '', None),
# …
]El script resuelve cada alias al hex de su primitivo, calcula la razón con la fórmula de WCAG 2.2 y termina con un error si un par no llega a su umbral. Si cambias un alias en la tabla, el script te dice qué par deja de cumplir antes de que llegue a la web.
python3 tools/semantic.pyEl script está en el repositorio: tools/semantic.py.
Lo que te llevas
- El contraste es de un par de colores, y se comprueba con el hex de cada modo y sin redondear.
- El nivel AA pide 4,5:1 para el texto normal; DesignToken101 lo exige a todo el texto porque un token no sabe a qué tamaño se usará.
- El comprobador de Figma sirve para lo que ves; para el sistema, la tabla de pares.