Verificador de Contraste de Color
Comprueba el ratio de contraste WCAG AA y AAA entre colores de primer plano y fondo
Color de primer plano (texto)
Color de fondo
Muestra de texto grande (18px negrita)
Muestra de texto normal — así aparecerá el cuerpo de texto sobre el color de fondo que hayas elegido.
Ratio de contraste
14.63:1
Conformidad WCAG
Aprobado
Normal AA
Aprobado
Normal AAA
Aprobado
Grande AA
Aprobado
Grande AAA
WCAG AA — Texto normal: 4.5:1, Texto grande (18px+ o 14px negrita): 3:1
WCAG AAA — Texto normal: 7:1, Texto grande (18px+ o 14px negrita): 4.5:1
¿Qué es la verificación de contraste de color?
La verificación de contraste de color mide la diferencia de luminancia entre un color de primer plano (habitualmente texto) y un color de fondo, y expresa el resultado como un ratio. Un ratio de 1:1 significa que los colores son idénticos; 21:1 es el máximo, que representa negro sobre blanco o viceversa. Las Pautas de Accesibilidad para el Contenido Web (WCAG), publicadas por el W3C, definen los ratios de contraste mínimos que debe cumplir el texto para que las personas con baja visión o deficiencias en la percepción del color puedan leerlo.
La fórmula del ratio de contraste proviene de WCAG 2.x y se basa en la luminancia relativa, una medida del brillo aparente de un color para el ojo humano. La luminancia relativa se calcula linealizando cada canal sRGB (eliminando la gamma) y ponderando los canales según los coeficientes ITU-R BT.709: 0,2126 para el rojo, 0,7152 para el verde y 0,0722 para el azul. El verde contribuye más porque el ojo humano es más sensible a la luz verde. El ratio resultante es (L1 + 0,05) / (L2 + 0,05), donde L1 es la luminancia del color más claro.
WCAG define dos niveles de conformidad. El nivel AA exige un ratio de contraste de al menos 4,5:1 para texto de tamaño normal y 3:1 para texto grande (18px o superior, o 14px en negrita). El nivel AAA eleva el listón a 7:1 y 4,5:1 respectivamente. WCAG 2.1 también introdujo el Criterio de Éxito 1.4.11, que requiere un ratio de 3:1 para componentes de interfaz no textuales como bordes, iconos e indicadores de foco.
¿Por qué usar este verificador de contraste?
Evaluar el contraste a simple vista no es fiable. Los colores que parecen diferenciados en tu monitor calibrado pueden confundirse en una pantalla de baja calidad, bajo la luz solar directa o para alguien con deuteranopía. Un ratio numérico elimina las conjeturas y te da un veredicto de aprobado o reprobado frente al estándar WCAG.
Casos de uso del verificador de contraste
Requisitos de ratio de contraste WCAG
La tabla siguiente resume los ratios de contraste mínimos exigidos por WCAG 2.1 para distintos tipos de contenido y niveles de conformidad. El texto grande se define como 18px (24 píxeles CSS) o superior con peso normal, o 14px (18,66 píxeles CSS) o superior con peso negrita.
| Nivel | Ratio mínimo | Aplica a | Nota |
|---|---|---|---|
| AA Normal text | 4.5 : 1 | Body text, paragraphs, labels | Minimum for most UI text |
| AA Large text | 3.0 : 1 | Text >= 18px, or >= 14px bold | Minimum for headings |
| AA UI components | 3.0 : 1 | Borders, icons, focus indicators | Non-text contrast (WCAG 1.4.11) |
| AAA Normal text | 7.0 : 1 | Body text at highest standard | Enhanced readability |
| AAA Large text | 4.5 : 1 | Large text at highest standard | Enhanced for headings |
Luminancia, AA y AAA explicados
El cálculo del ratio de contraste tiene tres etapas: calcular la luminancia de cada color, derivar el ratio y compararlo con los umbrales WCAG.
Ejemplos de código
Calcula ratios de contraste WCAG de forma programática. Cada ejemplo implementa la fórmula de luminancia relativa de WCAG 2.x y el cálculo del ratio de contraste. Se prueban los mismos pares negro sobre blanco e índigo sobre blanco para facilitar la comparación.
// Calculate relative luminance per WCAG 2.x (sRGB)
function luminance(r, g, b) {
const [rs, gs, bs] = [r, g, b].map(c => {
c /= 255
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
})
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
}
// Contrast ratio between two RGB colors
function contrastRatio(fg, bg) {
const l1 = luminance(...fg)
const l2 = luminance(...bg)
const lighter = Math.max(l1, l2)
const darker = Math.min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
contrastRatio([0, 0, 0], [255, 255, 255]) // -> 21.0
contrastRatio([99, 102, 241], [255, 255, 255]) // -> 3.95
contrastRatio([29, 78, 216], [255, 255, 255]) // -> 6.06 (AA pass)def luminance(r: int, g: int, b: int) -> float:
"""Relative luminance per WCAG 2.x, ITU-R BT.709 coefficients."""
channels = []
for c in (r, g, b):
c /= 255
channels.append(c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4)
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2]
def contrast_ratio(fg: tuple, bg: tuple) -> float:
l1 = luminance(*fg)
l2 = luminance(*bg)
lighter, darker = max(l1, l2), min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
print(f"{contrast_ratio((0, 0, 0), (255, 255, 255)):.2f}") # -> 21.00
print(f"{contrast_ratio((99, 102, 241), (255, 255, 255)):.2f}") # -> 3.95
# Check WCAG AA for normal text
ratio = contrast_ratio((29, 78, 216), (255, 255, 255))
print(f"{ratio:.2f} — {'AA pass' if ratio >= 4.5 else 'AA fail'}") # -> 6.06 — AA passpackage main
import (
"fmt"
"math"
)
func linearize(c float64) float64 {
c /= 255
if c <= 0.04045 {
return c / 12.92
}
return math.Pow((c+0.055)/1.055, 2.4)
}
func luminance(r, g, b int) float64 {
return 0.2126*linearize(float64(r)) +
0.7152*linearize(float64(g)) +
0.0722*linearize(float64(b))
}
func contrastRatio(fgR, fgG, fgB, bgR, bgG, bgB int) float64 {
l1 := luminance(fgR, fgG, fgB)
l2 := luminance(bgR, bgG, bgB)
lighter := math.Max(l1, l2)
darker := math.Min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
func main() {
ratio := contrastRatio(0, 0, 0, 255, 255, 255)
fmt.Printf("%.2f\n", ratio) // -> 21.00
ratio = contrastRatio(29, 78, 216, 255, 255, 255)
fmt.Printf("%.2f\n", ratio) // -> 6.06 (AA pass for normal text)
}/* WCAG-safe color pairs — tested contrast ratios */
/* 12.63:1 — passes AAA normal text */
.high-contrast {
color: #1e293b; /* slate-800 */
background: #f8fafc; /* slate-50 */
}
/* 7.07:1 — passes AAA normal text */
.dark-theme-text {
color: #e2e8f0; /* slate-200 */
background: #0f172a; /* slate-900 */
}
/* 4.57:1 — passes AA normal, fails AAA */
.accent-on-white {
color: #1d4ed8; /* blue-700 */
background: #ffffff;
}
/* 2.14:1 — fails AA for text, but passes 3:1 for large text */
.muted-heading {
color: #94a3b8; /* slate-400 */
background: #ffffff;
}