WCAG 2.1 AA · Flow Design System

Guía de Accesibilidad

Estándares, ratios de contraste, patrones de foco, navegación por teclado y buenas prácticas para que cada pieza de Flow sea usable por todas las personas.

1

Principios de Accesibilidad

Flow sigue WCAG 2.1 nivel AA como estándar mínimo. El objetivo: cero barreras para el 100% de los usuarios.

Perceptible

Toda la información y los componentes de la interfaz se presentan de manera que los usuarios puedan percibirlos — visual, auditiva o táctilmente.

Operable

Todos los componentes de navegación e interacción son operables mediante teclado, ratón, pantalla táctil y tecnologías asistivas.

Comprensible

La información y la operación de la interfaz son comprensibles — lenguaje claro, comportamiento predecible, ayuda contextual.

Robusto

El contenido es lo suficientemente robusto para ser interpretado por una variedad de agentes de usuario, incluidos lectores de pantalla.

Estándar Mínimo: WCAG 2.1 AA

Todos los deliverables de Flow — web, presentaciones, documentos, landing pages — deben cumplir al menos el nivel AA. Donde sea viable, apuntamos a AAA para ratios de contraste de texto.

2

Contraste de Color

Auditoría completa de las combinaciones de color de Flow contra los criterios WCAG 2.1.

Texto sobre Fondos Claros

Combinaciones principales del modo claro contra surface.primary (#f7f5f3).

Texto ejemplo Aa
14.7:1
text.primary sobre surface.primary
charcoal #0f2c24 → cream #f7f5f3
AA Normal ✓ AA Large ✓ AAA ✓
Texto ejemplo Aa
3.8:1
text.secondary sobre surface.primary
secondary #768782 → cream #f7f5f3
AA Normal ✗ AA Large ✓ AAA ✗
Texto ejemplo Aa
2.5:1
text.tertiary sobre surface.primary
tertiary #9faba7 → cream #f7f5f3
AA Normal ✗ AA Large ✗ AAA ✗
Texto ejemplo Aa
2.2:1
interactive.default sobre surface.primary
green-500 #3beb64 → cream #f7f5f3
AA Normal ✗ AA Large ✗ AAA ✗

Texto sobre Fondos Oscuros

Combinaciones sobre surface.inverse (#0f2c24).

Texto ejemplo Aa
13.4:1
text.inverse sobre surface.inverse
offwhite #f0eee9 → charcoal #0f2c24
AA Normal ✓ AA Large ✓ AAA ✓
Texto ejemplo Aa
6.7:1
text.accent sobre surface.inverse
green-500 #3beb64 → charcoal #0f2c24
AA Normal ✓ AA Large ✓ AAA ✗
Texto ejemplo Aa
5.3:1
text.inverse-secondary sobre surface.inverse
tertiary #9faba7 → charcoal #0f2c24
AA Normal ✓ AA Large ✓ AAA ✗

Feedback Colors

Error message
4.2:1
feedback.error sobre surface.primary
error #e84545 → cream #f7f5f3
AA Normal ✗ AA Large ✓
Warning message
2.4:1
feedback.warning sobre surface.primary
warning #f0a730 → cream #f7f5f3
AA Normal ✗ AA Large ✗

Reglas de Uso de Color

Green-500 (#3beb64) nunca como texto sobre fondo claro. Ratio 2.2:1 — falla todos los niveles WCAG. Usarlo solo como background de botón, borde activo o indicador decorativo.

Tertiary (#9faba7) solo como placeholder o texto deshabilitado en modo claro. Ratio 2.5:1 — no cumple para contenido informativo. Aceptable para hints decorativos con contexto adicional.

Warning (#f0a730) requiere siempre ícono acompañante. Ratio 2.4:1 — no confiar solo en el color para comunicar la advertencia.

Error (#e84545) usar en bold (≥18px) o acompañar con ícono. Pasa AA Large pero no AA Normal.

Componentes No-Texto (1.4.11)

Los componentes UI y gráficos requieren un mínimo de 3:1 contra el fondo adyacente.

Componente Color Fondo Ratio Estado
Botón primario #3beb64 #f7f5f3 2.2:1 ✗ Requiere borde
Borde de input #cfd5d3 #f7f5f3 1.4:1 ✗ Requiere label visible
Input focus ring #3beb64 #f7f5f3 2.2:1 ✓ + box-shadow visible
Checkbox marcado #2fd954 #f7f5f3 2.3:1 ✗ Requiere marca interior (✓)
Charcoal borde botón #0f2c24 #f7f5f3 14.7:1 ✓ Excelente

Compensación para Green-500 como Fondo de Botón

El botón primario (green-500 background) tiene texto charcoal que ofrece 6.7:1 de contraste interno. El botón como unidad es distinguible por su forma, sombra y label. Esto cumple WCAG 1.4.11 a través de múltiples indicadores visuales.

3

Gestión de Foco

Indicadores de foco claros y consistentes para todos los elementos interactivos.

Especificación del Focus Ring

Contexto Outline Offset Color
Botón primario (fondo verde) 3px solid 3px charcoal — contraste con verde
Botón secundario / ghost 3px solid 3px green-500
Links de texto 3px solid 3px green-500
Inputs border-color: green-500 + box-shadow: 0 0 0 3px rgba(59,235,100,0.12)
Sobre fondo oscuro 3px solid 3px green-500 — alta visibilidad sobre charcoal

Demo Interactiva — Usa Tab ↹ para Navegar

Presiona Tab para ver los indicadores de foco en acción.

Hacer
  • Usar :focus-visible para focus de teclado
  • Outline offset de 3px mínimo para separación
  • Invertir el color del ring según el fondo
  • Mantener un ratio mínimo de 3:1 del ring vs fondo
No Hacer
  • Quitar el outline sin reemplazo visible
  • Usar solo cambio de color como indicador
  • Aplicar outline: none globalmente
  • Confundir :focus con :focus-visible
/* ── SISTEMA DE FOCO FLOW ── */ /* Reset global — nunca quitar focus sin reemplazo */ *:focus { outline: none; } /* Focus visible para navegación por teclado */ *:focus-visible { outline: 3px solid var(--flow-color-interactive-default); outline-offset: 3px; } /* Inversión para elementos sobre fondo verde */ .btn-primary:focus-visible { outline-color: var(--flow-color-text-primary); } /* Input: ring suave en vez de outline duro */ input:focus, textarea:focus, select:focus { outline: none; border-color: var(--flow-color-interactive-default); box-shadow: 0 0 0 3px rgba(59, 235, 100, 0.12); }
4

Navegación por Teclado

Cada componente interactivo debe ser completamente operable sin ratón.

Mapa de Teclas por Componente

Componente Tecla Acción
Botón Tab Navegar al botón
Enter Activar el botón
Space Activar el botón
Modal Esc Cerrar modal
Tab Ciclar foco dentro del modal (focus trap)
Shift+Tab Ciclar foco en reversa
Dropdown / Select Navegar opciones
Enter Seleccionar opción activa
Esc Cerrar dropdown
Tabs Cambiar entre tabs
Tab Mover foco al contenido del tab activo
Toggle / Switch Space Cambiar estado on/off
Enter Cambiar estado on/off
Tooltip Tab (al trigger) Mostrar tooltip
Esc Ocultar tooltip
Toast Tab Navegar a acción del toast (si la hay)
Esc Descartar toast

Focus Trap — Modales y Diálogos

/* ── Focus Trap para modales ── */ function trapFocus(modal) { const focusable = modal.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const first = focusable[0]; const last = focusable[focusable.length - 1]; modal.addEventListener('keydown', (e) => { if (e.key !== 'Tab') return; if (e.shiftKey) { if (document.activeElement === first) { e.preventDefault(); last.focus(); } } else { if (document.activeElement === last) { e.preventDefault(); first.focus(); } } }); first.focus(); // Foco inicial al primer elemento }

Regla de Orden de Tab

El orden de tabulación debe seguir el flujo visual del contenido. Nunca usar tabindex mayor a 0. Usar solo tabindex="0" (orden natural) o tabindex="-1" (removible del flujo, enfocable por JS).

5

HTML Semántico

Estructura correcta para lectores de pantalla y SEO.

Estructura de Página Base

<header role="banner">
<nav aria-label="Navegación principal"> ... </nav>
</header>
<main id="main-content">
<section aria-labelledby="section-title">
<h2 id="section-title"> ... </h2>
<article> ... </article> ← contenido independiente
</section>
<aside aria-label="Contenido relacionado"> ... </aside>
</main>
<footer role="contentinfo"> ... </footer>

Jerarquía de Encabezados

Correcto
  • Un solo <h1> por página
  • Niveles secuenciales: h1 → h2 → h3
  • Cada sección con su propio h2
  • Encabezados descriptivos del contenido
Incorrecto
  • Saltar niveles: h1 → h3 → h5
  • Usar encabezados por estilo visual
  • Múltiples h1 en la misma página
  • Encabezados vacíos o genéricos

Elementos vs. Roles

Elemento HTML Rol implícito Cuándo usar
<button> button Acciones en la página (no navegación)
<a href> link Navegación a otra página o sección
<nav> navigation Grupos de enlaces de navegación
<main> main Contenido principal (1 por página)
<ul> / <ol> list Listas de ítems (lectores anuncian conteo)
<img alt="..."> img Imágenes con texto alternativo obligatorio
<table> table Datos tabulares (nunca para layout)

Regla de Oro: Elemento Nativo Primero

Siempre preferir el elemento HTML nativo sobre role="...". Un <button> ya tiene el role, los listeners de teclado, y el estado accesible. Un <div role="button"> requiere recrear todo manualmente.

6

Patrones ARIA

Atributos ARIA esenciales para componentes de Flow.

Atributos por Componente

Componente Atributos ARIA Notas
Modal role="dialog", aria-modal="true", aria-labelledby Referenciar el título con aria-labelledby
Dropdown aria-expanded, aria-haspopup="listbox", aria-controls Actualizar expanded al abrir/cerrar
Tabs role="tablist", role="tab", role="tabpanel", aria-selected Un solo tab con aria-selected="true"
Toggle role="switch", aria-checked Actualizar checked al cambiar
Toast role="status", aria-live="polite" Polite para info, assertive para errores
Tooltip role="tooltip", aria-describedby Vincular trigger con describedby
Loading aria-busy="true", aria-live="polite" Anunciar cuando termine la carga
Accordion aria-expanded, aria-controls El trigger indica si está abierto/cerrado

aria-live — Regiones Dinámicas

Niveles de Urgencia

Valor Comportamiento Usar para
polite Espera a que el lector termine de hablar Toasts informativos, actualizaciones de estado, contadores
assertive Interrumpe inmediatamente Errores críticos, alertas de seguridad, validaciones de formulario
off No se anuncia Contenido que cambia frecuentemente (ticker, reloj)
/* ── Ejemplo: Toast con aria-live ── */ <div id="toast-container" role="status" aria-live="polite" aria-atomic="true" > <!-- JS inyecta el mensaje aquí --> <div class="toast toast--success"> Cambios guardados correctamente. </div> </div> /* Para errores, usar assertive: */ <div role="alert" aria-live="assertive" > Error: No se pudo guardar. Intenta de nuevo. </div>
7

Formularios Accesibles

Labels, errores, y patrones de validación que funcionan para todos.

Reglas Fundamentales

Hacer
  • Toda input con <label for="id"> visible
  • Texto de error debajo del campo + aria-describedby
  • Campo inválido: aria-invalid="true"
  • Campos obligatorios: aria-required="true" + indicador visual (*)
  • Agrupar campos relacionados con <fieldset> + <legend>
No Hacer
  • Placeholder como único label (desaparece al escribir)
  • Errores solo con color rojo (invisible para daltónicos)
  • Validación solo al submit (feedback tardío)
  • Labels genéricos: "Campo 1", "Dato"
  • Deshabilitar el botón de submit sin explicación

Patrón de Campo con Error

<div class="field"> <label for="email"> Correo electrónico <span aria-hidden="true">*</span> </label> <input type="email" id="email" aria-required="true" aria-invalid="true" aria-describedby="email-error" /> <div id="email-error" role="alert" class="field-error" > <!-- ícono + texto = doble indicador --> ⚠ Ingresa un correo válido (ej: nombre@empresa.com) </div> </div>

Estilos de Error (Token-Aligned)

.field-error { color: var(--flow-color-feedback-error); /* #e84545 */ font-size: 0.82rem; font-weight: 600; margin-top: var(--flow-spacing-xs); /* 4px */ display: flex; align-items: center; gap: 6px; } input[aria-invalid="true"] { border-color: var(--flow-color-feedback-error); box-shadow: 0 0 0 3px rgba(232, 69, 69, 0.12); }
8

Movimiento Reducido

Respetar la preferencia del sistema operativo del usuario. Referencia completa: motion-guide.html § 8.

/* ── Enfoque nuclear: quitar todo ── */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } /* ── Enfoque granular (preferido): preservar transiciones esenciales ── */ @media (prefers-reduced-motion: reduce) { /* Quitar animaciones decorativas */ .hero-animation, .parallax, .particle-bg { animation: none; } /* Mantener cambios de estado funcionales, pero instantáneos */ .btn, .card, .input, .toggle { transition-duration: 0.01ms; } /* Mantener opacity para fades de contenido */ .modal, .toast, .dropdown { transition-property: opacity; transition-duration: 150ms; } }
Hacer
  • Respetar prefers-reduced-motion siempre
  • Mantener cambios de estado (visible/oculto) funcionales
  • Ofrecer toggle manual de animaciones si es posible
  • Fades suaves (opacity) son aceptables en modo reducido
No Hacer
  • Animaciones que parpadean más de 3 veces/segundo
  • Movimiento continuo sin pausa (carruseles infinitos)
  • Parallax obligatorio
  • Video auto-play sin controles de pausa
9

Contenido Accesible

Imágenes, multimedia, y contenido textual para todos.

Texto Alternativo para Imágenes

Tipo de Imagen Alt Text Ejemplo
Informativa Descripción concisa del contenido alt="Equipo de Flow en sesión de diseño colaborativo"
Decorativa Alt vacío alt="" + role="presentation"
Funcional (botón/link) Describir la acción, no la imagen alt="Cerrar modal" no alt="Ícono de X"
Compleja (gráfico/datos) Resumen breve + descripción larga alt="Crecimiento Q1-Q4" + aria-describedby="chart-desc"
Texto como imagen Reproducir el texto exacto alt="Flow Collective Studio"

Iconografía Accesible

Flow usa Lucide Icons (stroke 1.5px). Reglas:

/* ── Ícono decorativo (junto a texto) ── */ <button> <svg aria-hidden="true" focusable="false"> ... </svg> Guardar </button> /* ── Ícono funcional (sin texto) ── */ <button aria-label="Cerrar"> <svg aria-hidden="true" focusable="false"> ... </svg> </button> /* ── Ícono informativo (comunica estado) ── */ <svg role="img" aria-label="Estado: completado"> ... </svg>

Contenido Multimedia

Video

  • Subtítulos siempre (captions cerradas)
  • Audio-descripción para contenido visual
  • Controles de reproducción accesibles
  • Nunca auto-play con audio
  • Transcript disponible

Audio

  • Transcripción completa disponible
  • Controles de pausa/play accesibles
  • Indicar duración antes de reproducir
  • Nunca audio-only para info crítica

Lenguaje Claro

Hacer
  • Lenguaje simple y directo (nivel B2 máximo)
  • Párrafos cortos (3-4 líneas máximo)
  • Links descriptivos: "Ver resultados completos"
  • Definir acrónimos en primer uso
No Hacer
  • Links genéricos: "Clic aquí", "Leer más"
  • Bloques de texto de más de 80 caracteres por línea
  • Texto justificado (dificulta lectura)
  • Todo mayúsculas para párrafos
10

Checklist de Entrega

Lista de verificación obligatoria antes de entregar cualquier deliverable de Flow.

Contraste & Color

  • Texto principal pasa WCAG AA (4.5:1 mínimo)
  • Texto grande pasa AA (3:1 mínimo, ≥18px bold o ≥24px normal)
  • Green-500 nunca usado como texto sobre fondo claro
  • Información no depende solo del color (ícono o texto acompañante)
  • Feedback colors con indicador adicional (ícono, texto, borde)

Interacción & Foco

  • Todos los interactivos son accesibles por teclado
  • Focus ring visible con 3:1 vs fondo adyacente
  • Orden de tab sigue el flujo visual del documento
  • Focus trap implementado en modales y diálogos
  • Skip links disponibles ("Ir al contenido principal")
  • Sin componentes que requieran exclusivamente hover

Estructura & Semántica

  • HTML semántico (header, main, nav, section, footer)
  • Un solo h1 por página, jerarquía sin saltos
  • Landmarks ARIA donde el HTML nativo no alcanza
  • Tablas de datos con <th scope> y <caption>
  • Atributo lang="es" en el HTML

Formularios

  • Toda input tiene label visible asociado
  • Errores con aria-invalid + aria-describedby
  • Mensajes de error con ícono + texto (no solo color)
  • Campos obligatorios indicados con * y aria-required
  • Fieldsets + legends para grupos de campos

Contenido & Media

  • Imágenes informativas con alt descriptivo
  • Imágenes decorativas con alt=""
  • Íconos funcionales con aria-label
  • Video con subtítulos y transcripción
  • Sin auto-play de audio/video
  • Links descriptivos (no "clic aquí")

Movimiento & Responsividad

  • Respeta prefers-reduced-motion
  • Sin flashes más de 3 veces/segundo
  • Texto escalable a 200% sin pérdida
  • Funcional en viewport de 320px mínimo
  • Touch targets mínimo 44×44px en móvil

Herramientas de Auditoría Recomendadas

Automatizadas: axe DevTools, Lighthouse Accessibility, WAVE, Pa11y

Manual: Navegar con Tab, probar con VoiceOver (Mac) / NVDA (Windows), verificar zoom 200%

Contraste: WebAIM Contrast Checker, Colour Contrast Analyser (CCA)