Construyendo un Nutritional Tracker: Parte 6 — Arquitectura Visual y Accesibilidad
Construyendo un Nutritional Tracker: Parte 6 — Arquitectura Visual y Accesibilidad
Introducción
Tras completar un formulario con validación y persistencia, surgió la pregunta inevitable: ¿cómo se ve esto en manos del usuario?
La respuesta rápida fue honesta: mal. HTML sin estilos produce interfaces frustrantes: elementos táctiles pequeños, contraste bajo y navegación poco clara. En esta entrega documenté cómo llevé la UI desde “cruda” a una capa visual consistente, mobile-first, accesible y fácil de mantener.
Aquí encontrarás las decisiones clave, fragmentos de implementación y pruebas que me dieron confianza para entregar una experiencia usable en móviles y escritorio.
(Coloca aquí la captura del formulario ANTES de aplicar estilos — HTML crudo, desalineado)
(Coloca aquí la captura del formulario DESPUÉS — móvil, limpio y moderno)
Mis lineamientos de diseño
Antes de tocar una clase de Tailwind, definí principios que guiaron todas las decisiones. Los tengo en un archivo llamado lineamientos-estilos.md y resumo lo esencial:
Principios fundamentales
- Mobile‑first por defecto (alto % de uso móvil).
- Targets táctiles ≥ 48 px (mejor usabilidad y accesibilidad).
- Contraste mínimo WCAG AA.
- Labels siempre visibles — nunca usar placeholder como única etiqueta.
- Feedback inmediato: validación inline y mensajes claros.
Decisiones específicas para inputs
- Formularios cortos: ideal 3–5 campos por pantalla.
- Evitar selects para listas muy cortas → preferir radios.
- Aprovechar tipos nativos:
type="number",type="date", etc. - En móvil: layout de columna única para facilitar el scroll y foco.
Por qué TailwindCSS v4
La elección fue pragmática: rapidez de prototipado y consistencia sin sobresalto de CSS personalizado.
Comparación rápida:
- CSS Modules: control total, pero mucho boilerplate.
- Styled Components: integración con React, pero añade peso.
- CSS vanilla: cero dependencias, pero lento para iterar.
- Tailwind v4: iteración ultrarrápida, pequeño bundle y mentalidad mobile-first.
Configuración esencial (tailwind.config.cjs)
// tailwind.config.cjs (extracto)
module.exports = {
theme: {
extend: {
colors: {
primary: { DEFAULT: '#0ea5e9', light: '#38bdf8', dark: '#0369a1' },
secondary: { DEFAULT: '#f59e42', light: '#fbbf24', dark: '#b45309' },
error: { DEFAULT: '#ef4444', bg: '#fee2e2' },
success: { DEFAULT: '#22c55e', bg: '#dcfce7' },
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
heading: ['Montserrat', 'Inter', 'sans-serif'],
},
},
},
}Componentes UI reutilizables
El problema más repetido era: repetir 7 veces el mismo patrón label + input + error. La solución fue crear componentes con responsabilidad única.
Antes: duplicación y riesgo de inconsistencias
<label className="block text-sm font-medium text-gray-700">
Alimento <span className="text-red-600">*</span>
</label>
<input className="mt-1 w-full px-4 py-2 border border-gray-300 rounded-md" />
<span className="text-sm text-red-600">Campo requerido</span>Después: componentes con API clara
- Label.tsx — muestra el label, el asterisco accesible y el sr-only para lectores de pantalla:
<label className="block text-sm text-gray-700 font-medium">
{children}
{required && (
<>
<span aria-hidden="true" className="text-red-600"> *</span>
<span className="sr-only"> requerido</span>
</>
)}
</label>Input.tsx — con forwardRef, manejo de estados (default, error, focus) y clases compartidas:
RadioGroup — sustituyó selects en muchos casos; mejor para móviles por targets grandes:
<RadioGroup
name="mealType"
options={mealTypeOptions.map(type => ({
value: type,
label: MEAL_TYPE_LABELS[type],
}))}
layout="grid"
/>Accesibilidad: teoría aplicada y pruebas automáticas
No basta con pensar “es accesible”; hay que medirlo. Integré axe-core en los tests unitarios para validar accesibilidad en estados dinámicos.
Ejemplo de test (Jest + Testing Library + axe):
it('formulario con errores visibles sigue accesible', async () => {
// ... simular submit vacío y mostrar errores
const results = await runAxe(container)
expect(results.violations).toHaveLength(0)
})Resultado real en CI: 0 violaciones incluso con errores visibles.
También probé navegación por teclado y foco visible en DevTools:
Storybook: QA visual y documentación viva
Storybook me permitió explorar estados concretos y automatizar interacciones:
- addons: a11y y interactions.
- stories útiles que mantengo activas:
- Button: Primary / Secondary / Loading / Disabled
- Input: Default / Error / Typing (con play function)
- RadioGroup: Vertical / Grid / Error
- RegistrationForm: MinimalSubmit (flujo feliz automatizado)
Estado actual del proyecto
Lo que ya está implementado:
- ✅ Formulario 100% mobile-first con targets táctiles adecuados
- ✅ Contraste validado automáticamente (axe-core = 0 violaciones)
- ✅ Componentes documentados y testeados en Storybook
Próximos pasos:
- Visualización de datos (gráficos y reportes)
- Búsqueda y filtros en el historial
Reflexión final
Próxima parte → Parte 7: Visualización de datos y reportes.