Ver contenido
Optimización de Contraste de Color: Cumpliendo Estándares WCAG AA y AAA
Introducción
El contraste de color es fundamental para la accesibilidad web, afectando si los usuarios pueden percibir e interactuar con tu contenido. Esta guía completa cubre los requisitos técnicos para cumplimiento WCAG, metodologías de testing, estrategias de diseño y patrones de implementación para lograr estándares de contraste Nivel AA y AAA.
Entendiendo los Requisitos de Contraste
Matriz de Contraste WCAG
| Tipo de Contenido | Nivel AA | Nivel AAA | Criterio WCAG |
|---|---|---|---|
| Texto normal | 4.5:1 | 7:1 | 1.4.3 / 1.4.6 |
| Texto grande (≥18pt/14pt negrita) | 3:1 | 4.5:1 | 1.4.3 / 1.4.6 |
| Componentes UI | 3:1 | N/A | 1.4.11 |
| Objetos gráficos | 3:1 | N/A | 1.4.11 |
| Indicadores de foco | 3:1 | N/A | 1.4.11 |
| Subrayados de enlaces | 3:1 | N/A | 1.4.1 |
La Fórmula de Contraste
La relación de contraste se calcula usando luminancia relativa:
Relación de Contraste = (L1 + 0.05) / (L2 + 0.05)
Donde:
L1 = Luminancia relativa del color más claro
L2 = Luminancia relativa del color más oscuro
Luminancia Relativa = 0.2126 * R + 0.7152 * G + 0.0722 * B
Para cada componente RGB:
Si sRGB ≤ 0.04045: valor = sRGB / 12.92
Si sRGB > 0.04045: valor = ((sRGB + 0.055) / 1.055) ^ 2.4
Nota: Usa herramientas automatizadas en lugar de cálculo manual.
Herramientas de Testing
DevTools del Navegador
Chrome DevTools:
1. Inspeccionar elemento
2. Click en muestra de color en panel Estilos
3. Ver sección "Contrast ratio"
4. Ver estado de cumplimiento AA/AAA
Herramientas de Línea de Comandos
# Usando Pa11y para verificación de contraste
npx pa11y https://ejemplo.com --include-notices --include-warnings
# Usando axe-cli
npx @axe-core/cli https://ejemplo.com
Testing Automatizado
// Cypress con axe-core
import 'cypress-axe';
describe('Tests de Contraste', () => {
beforeEach(() => {
cy.visit('/');
cy.injectAxe();
});
it('no debe tener violaciones de contraste', () => {
cy.checkA11y(null, {
rules: {
'color-contrast': { enabled: true }
}
});
});
});
Plugins de Herramientas de Diseño
| Herramienta | Plugin | Características |
|---|---|---|
| Figma | Stark | Simulación daltonismo, verificación contraste |
| Figma | A11y - Color Contrast | Relación de contraste en tiempo real |
| Sketch | Stark | Igual que versión Figma |
| Adobe XD | Color Contrast Analyzer | Verificador de contraste integrado |
Violaciones Comunes y Correcciones
Problemas de Contraste de Texto
Problema: Texto Gris Claro
/* FALLA AA - relación 2.85:1 */
.texto-atenuado {
color: #999999;
background-color: #ffffff;
}
/* PASA AA - relación 4.54:1 */
.texto-atenuado {
color: #767676;
background-color: #ffffff;
}
/* PASA AAA - relación 7.0:1 */
.texto-atenuado {
color: #595959;
background-color: #ffffff;
}
Problema: Texto Placeholder
/* FALLA - Placeholders predeterminados a menudo tienen bajo contraste */
input::placeholder {
color: #c0c0c0; /* 1.9:1 */
}
/* PASA AA - Placeholder mejorado */
input::placeholder {
color: #757575; /* 4.6:1 */
}
/* Alternativa: Usar etiquetas visibles en lugar de placeholders */
Problema: Texto Coloreado sobre Fondo Coloreado
/* FALLA - Azul sobre azul claro: 2.1:1 */
.aviso {
color: #4a90d9;
background-color: #e3f2fd;
}
/* PASA AA - Azul más oscuro: 4.6:1 */
.aviso {
color: #1565c0;
background-color: #e3f2fd;
}
Contraste de Componentes UI (WCAG 1.4.11)
Botones
/* FALLA - Borde de botón muy claro */
.btn-outline {
border: 1px solid #cccccc; /* 1.6:1 contra blanco */
color: #333333;
background: transparent;
}
/* PASA - Contraste de borde suficiente */
.btn-outline {
border: 2px solid #767676; /* 4.5:1 contra blanco */
color: #333333;
background: transparent;
}
Campos de Formulario
/* FALLA - Bordes de input se mezclan con fondo */
input {
border: 1px solid #e0e0e0; /* 1.4:1 */
background: #ffffff;
}
/* PASA - Límites de input visibles */
input {
border: 1px solid #767676; /* 4.5:1 */
background: #ffffff;
}
/* Alternativa: Usar diferencia de color de fondo */
input {
border: 1px solid #e0e0e0;
background: #f5f5f5; /* Crea límite visual */
}
Indicadores de Foco
/* FALLA - Visibilidad de foco insuficiente */
:focus {
outline: 1px solid #cccccc;
}
/* PASA - Indicador de foco claro */
:focus {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
/* Mejorado: Múltiples señales visuales */
:focus {
outline: 3px solid #005fcc;
outline-offset: 2px;
box-shadow: 0 0 0 4px rgba(0, 95, 204, 0.2);
}
Enlaces
/* FALLA - Enlaces distinguidos solo por color */
a {
color: #0066cc;
text-decoration: none;
}
/* PASA - Indicador visual adicional */
a {
color: #0066cc;
text-decoration: underline;
}
/* Alternativa: Otras señales visuales */
a {
color: #0066cc;
text-decoration: none;
border-bottom: 1px solid currentColor;
}
a:hover,
a:focus {
text-decoration: underline;
}
Estrategias de Diseño
Creando Paletas de Color Accesibles
// Colores base con variantes seguras para contraste
$colors: (
primary: (
50: #e3f2fd, // Fondo
500: #2196f3, // Solo decorativo
700: #1565c0, // Texto sobre blanco (4.6:1)
900: #0d47a1 // Texto sobre blanco (8.0:1)
),
gray: (
100: #f5f5f5, // Fondo claro
500: #9e9e9e, // Solo decorativo
600: #757575, // Texto sobre blanco (4.6:1)
900: #212121 // Texto alto contraste
)
);
Diseño Amigable para Daltonismo
/* No dependas solo del color */
/* MAL: Error indicado solo por color rojo */
.error {
color: red;
}
/* BIEN: Múltiples indicadores */
.error {
color: #c62828;
font-weight: bold;
}
.error::before {
content: "⚠ ";
}
/* O usa íconos */
.mensaje-error {
display: flex;
align-items: center;
gap: 0.5rem;
color: #c62828;
}
.mensaje-error svg {
flex-shrink: 0;
}
Consideraciones de Modo Oscuro
/* Modo claro */
:root {
--text-primary: #212121; /* 15.4:1 sobre blanco */
--text-secondary: #757575; /* 4.6:1 sobre blanco */
--background: #ffffff;
--surface: #f5f5f5;
}
/* Modo oscuro */
@media (prefers-color-scheme: dark) {
:root {
--text-primary: #e0e0e0; /* 12.6:1 sobre fondo oscuro */
--text-secondary: #9e9e9e; /* 5.0:1 sobre fondo oscuro */
--background: #121212;
--surface: #1e1e1e;
}
}
/* Aplicar consistentemente */
body {
color: var(--text-primary);
background-color: var(--background);
}
Degradados e Imágenes
/* Asegurar que el texto sobre degradados/imágenes permanezca legible */
/* Opción 1: Overlay */
.hero-text {
position: relative;
z-index: 1;
}
.hero::before {
content: '';
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 0;
}
/* Opción 2: Sombra de texto */
.hero-text {
color: #ffffff;
text-shadow:
0 1px 2px rgba(0, 0, 0, 0.8),
0 0 20px rgba(0, 0, 0, 0.5);
}
/* Opción 3: Fondo detrás del texto */
.hero-text {
background: rgba(0, 0, 0, 0.7);
padding: 1rem 2rem;
color: #ffffff;
}
Patrones de Implementación
Propiedades Personalizadas CSS para Tematización
:root {
/* Sistema de colores seguro para contraste */
--color-text: hsl(0, 0%, 13%); /* #212121 */
--color-text-muted: hsl(0, 0%, 46%); /* #757575 */
--color-background: hsl(0, 0%, 100%); /* #ffffff */
--color-border: hsl(0, 0%, 46%); /* #757575 */
--color-primary: hsl(210, 79%, 46%); /* #1976d2 */
--color-primary-text: hsl(210, 79%, 35%);/* Para uso en texto */
--color-focus: hsl(210, 100%, 40%); /* #005fcc */
}
/* Uso */
body {
color: var(--color-text);
background: var(--color-background);
}
.link {
color: var(--color-primary-text);
}
.border {
border: 1px solid var(--color-border);
}
:focus-visible {
outline: 2px solid var(--color-focus);
}
Verificaciones CI/CD Automatizadas
# Workflow de GitHub Actions
name: Tests de Accesibilidad
on: [push, pull_request]
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Instalar dependencias
run: npm ci
- name: Build
run: npm run build
- name: Ejecutar Pa11y
run: |
npx pa11y-ci --config .pa11yci.json
# .pa11yci.json
{
"defaults": {
"standard": "WCAG2AA",
"runners": ["axe"],
"threshold": 0
},
"urls": [
"http://localhost:3000/",
"http://localhost:3000/productos",
"http://localhost:3000/contacto"
]
}
Documentación del Sistema de Diseño
## Guías de Contraste de Color
### Colores de Texto
| Uso | Color | Hex | Fondo Mín | Relación |
|-----|-------|-----|-----------|----------|
| Texto primario | Gray 900 | #212121 | Blanco | 15.4:1 |
| Texto secundario | Gray 600 | #757575 | Blanco | 4.6:1 |
| Enlaces | Blue 700 | #1565c0 | Blanco | 4.6:1 |
### Hacer y No Hacer
✅ HACER:
- Usar Gray 600 (#757575) o más oscuro para texto
- Agregar subrayados a enlaces
- Probar todas las combinaciones de colores
❌ NO HACER:
- Usar colores más claros que Gray 600 para texto
- Depender solo del color para significado
- Usar texto placeholder como etiquetas
Monitoreo y Mantenimiento
Checklist de Auditoría de Contraste
## Auditoría Mensual de Contraste
- [ ] Ejecutar escaneo automatizado (axe, Pa11y)
- [ ] Verificar nuevas páginas/componentes
- [ ] Verificar contraste en modo oscuro
- [ ] Probar en dispositivos móviles reales
- [ ] Revisar colores del sistema de diseño
- [ ] Verificar embeds de terceros
Manejando Casos Especiales
/* Estados deshabilitados - exentos de requisitos de contraste */
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Texto decorativo - exento si es puramente estético */
.watermark {
/* No necesita cumplir contraste si no es esencial */
}
/* Logos - exentos de requisitos de contraste */
.logo {
/* Logos de marca no necesitan cumplir contraste */
}
Referencia Rápida
Combinaciones de Colores Seguras
| Fondo | Colores de Texto Seguros |
|---|---|
| #ffffff (blanco) | #767676 o más oscuro (4.5:1+) |
| #f5f5f5 (gray 100) | #616161 o más oscuro (4.5:1+) |
| #000000 (negro) | #949494 o más claro (4.5:1+) |
| #1565c0 (blue 700) | #ffffff (4.6:1) |
Artículos Relacionados
- Contraste de Color Explicado - Introducción al contraste
- Hub de Cumplimiento WCAG - Guía completa de accesibilidad
- Indicadores de Foco Explicado - Accesibilidad de estados de foco
Referencias
- W3C - WCAG 2.2 SC 1.4.3 Contraste (Mínimo)
- W3C - WCAG 2.2 SC 1.4.11 Contraste No Textual
- WebAIM - Contraste y Accesibilidad del Color
- Deque - Analizador de Contraste de Color
- A11Y Project - Contraste de Color