Buenas prácticas de frontend para una UI limpia y escalable
Arquitectura frontend, accesibilidad, rendimiento y sistemas de diseño prácticos para construir interfaces que se mantengan sostenibles a medida que el producto crece.
Una interfaz limpia no se logra puliendo detalles al final. Nace de un sistema en el que la jerarquía de contenido, los límites de los componentes, los estados de interacción, la accesibilidad y el rendimiento apuntan a la misma tarea del usuario.
Una UI escalable tampoco significa construir la biblioteca de componentes más abstracta posible. Significa que el equipo puede añadir funciones sin generar comportamientos inconsistentes, decisiones duplicadas o miedo creciente a modificar el código existente.
Estas prácticas ofrecen una base sólida para aplicaciones con React, Next.js y TypeScript, pero la mayoría se aplican a cualquier stack frontend moderno.
Empieza por la tarea del usuario
Antes de elegir componentes, define la tarea principal y la información que se necesita para completarla. Un dashboard debe permitir escanear, comparar, filtrar y repetir acciones. Una página de contenido debe favorecer la lectura y la navegación. Un formulario debe hacer que completar y recuperar sea predecible.
Esto afecta a la arquitectura. Si los usuarios comparan filas, el diseño necesita columnas estables y estados claros. Si vigilan datos que cambian, la interfaz necesita marcas de tiempo, comportamiento de actualización y gestión de datos obsoletos. Si un flujo tiene varios pasos, la navegación y la persistencia del borrador pasan a formar parte del producto, no de la decoración.
Escribe criterios de aceptación para los estados de carga, vacío, parcial, error, restricción por permisos y éxito. Los equipos suelen diseñar solo el estado ideal, aunque los usuarios pasan tiempo relevante en todos los demás.
Usa HTML semántico como capa base
El HTML semántico reduce el código a medida y comunica la estructura al navegador, a los motores de búsqueda y a las tecnologías de asistencia.
Usa enlaces para navegar y botones para actuar. Asocia cada campo con su etiqueta. Usa encabezados para describir la estructura de la página, no para obtener un tamaño de fuente. Agrupa elementos relacionados en listas. Usa controles nativos como details, dialog y los tipos de input adecuados cuando su comportamiento encaje con el requisito.
Este enfoque hace que la mejora progresiva sea viable. El contenido principal y la navegación siguen disponibles antes de que cargue JavaScript o si este falla. JavaScript aporta comodidad en lugar de ser la única vía hacia la información.
Consulta las características de accesibilidad implementadas en este sitio como línea base.
Elige los límites de los componentes según el comportamiento
Un componente merece su propio límite cuando encapsula un comportamiento coherente, un patrón visual que se repite o una unidad semántica con significado. Evita separar cada envoltorio en un componente; una fragmentación excesiva vuelve difícil de entender la estructura renderizada.
Límites útiles suelen ser:
- Un campo que gestiona su etiqueta, descripción, error y la relación con el input
- Un diálogo que gestiona la entrada del foco, el cierre y su restauración
- Una tabla que gestiona la ordenación y la selección
- Un componente de navegación que gestiona la semántica de página actual
- Una tarjeta reutilizada con la misma jerarquía de información
Mantén el lenguaje específico del producto cerca de la funcionalidad. Un Card universal con decenas de props opcionales puede volverse menos reutilizable que un pequeño ProjectSummary con un contrato claro.
Componer suele ser más fácil de extender que una lista creciente de props booleanas. Si un componente admite muchos layouts e interacciones sin relación, probablemente representa varios componentes que comparten una carcasa visual, no una única abstracción coherente.
Establece tokens antes de que se multipliquen las variantes
Los tokens de diseño convierten decisiones repetidas en un vocabulario controlado. Empieza por los valores que realmente se repiten:
- Colores de texto, superficie, borde, acento, éxito, aviso y error
- Una escala de espaciado contenida
- Tipografía de cuerpo, etiqueta, encabezado y código
- Radios de borde y niveles de elevación
- Anchos de contenido y breakpoints responsivos
- Tratamiento del anillo de foco
- Duración y easing de las animaciones
Nombra los tokens por propósito cuando sea posible. --text-muted comunica más que --gray-500, sobre todo cuando cambian los temas. Los tokens a nivel de componente pueden mapearse a los tokens del sistema sin exponer los valores crudos por todo el código.
Un sistema de tokens no está completo hasta que cubre los estados de interacción. Hover, focus, active, selected, disabled, loading y error deben sentirse relacionados a lo largo del producto.
La página de habilidades enumera las tecnologías utilizadas para implementar estos sistemas.
Mantén el estado cerca y los límites de datos explícitos
El estado de interacción local debe quedarse cerca del componente que lo usa. Elévalo cuando varias partes de una funcionalidad necesiten un dueño compartido. Usa el estado en la URL para filtros, pestañas, búsquedas y paginación que el usuario deba poder marcar o compartir.
Separa los datos del servidor del estado temporal de la interfaz. Una caché de consultas, una librería de formularios, la URL y un store global tienen responsabilidades distintas. Meterlo todo en un único store global puede dificultar razonar sobre las actualizaciones y duplicar datos que ya pertenecen a otra parte.
Valida los datos externos en el límite. TypeScript describe lo que la aplicación espera en tiempo de compilación; no demuestra que una respuesta de API, un valor de local-storage o un parámetro de URL tengan esa forma en tiempo de ejecución.
Representa los estados importantes de forma explícita. Una unión discriminada puede evitar combinaciones imposibles como mostrar datos cargados y un error fatal de carga a la vez.
Haz que la accesibilidad forme parte de la aceptación del componente
La accesibilidad debe probarse con el comportamiento del componente, no aplazarse a una auditoría final.
Para cada patrón interactivo, verifica que:
- El control tiene un nombre accesible
- El comportamiento con teclado cumple las expectativas del usuario
- El foco es visible y se mueve de forma deliberada
- Los mensajes de estado y error se anuncian cuando corresponde
- El texto y los controles tienen suficiente contraste
- El significado no depende solo del color
- El zoom y el reajuste de texto no ocultan funciones
- La animación respeta
prefers-reduced-motion
Las pruebas automatizadas detectan etiquetas ausentes, relaciones inválidas y muchos problemas de contraste. Las revisiones manuales con teclado y lector de pantalla siguen siendo necesarias para la calidad de la interacción y el orden de los anuncios.
Integra el rendimiento en la arquitectura
Mantener el rendimiento es más fácil cuando la arquitectura por defecto envía menos trabajo.
Renderiza contenido estático en build o en el servidor cuando no dependa del estado del navegador. Hidrata solo las regiones interactivas. Somete los scripts de terceros a revisión explícita. Usa imágenes responsivas con dimensiones declaradas. Carga la imagen más importante de forma deliberada y aplaza el contenido que empieza muy por debajo del viewport.
Mide los Core Web Vitals actuales:
- LCP refleja la carga del mayor elemento significativo.
- INP refleja la capacidad de respuesta ante interacciones.
- CLS refleja el movimiento inesperado durante el ciclo de vida de la página.
Un buen resultado en laboratorio es útil, pero los datos de campo en producción reflejan dispositivos reales, condiciones de red y sesiones. Usa ambos cuando estén disponibles.
Evita memoizar prematuramente en React. Primero reduce el estado innecesario, mantén límites de render razonables y perfila la interacción real. La optimización debe responder a evidencia o a una estrategia de compilador establecida.
Define una puerta de calidad que el equipo pueda ejecutar
Una interfaz mantenible necesita chequeos repetibles. Una puerta práctica para las pull requests incluye:
- Formato y linting
- Verificación estricta de tipos
- Pruebas unitarias e integración enfocadas
- Un build de producción
- Chequeos automatizados de accesibilidad
- Pruebas responsivas en navegador
- Presupuestos de bundle o rendimiento para rutas sensibles
La checklist de revisión de código frontend desarrolla este flujo como secuencia de revisión.
Documenta las decisiones de arquitectura que si no acabarían debatiéndose de nuevo: ownership del estado, convenciones de rutas, navegadores soportados, ubicación de componentes, manejo de errores y criterios para dependencias. Una documentación corta y actualizada es más útil que un manual largo en el que nadie confía.
Prefiere una consistencia que sirva a la claridad
La consistencia vale porque reduce carga cognitiva, no porque toda pantalla deba verse idéntica. Acciones similares deben usar etiquetas, posiciones, feedback y comportamiento con teclado similares. Las excepciones intencionadas son razonables cuando la tarea cambia.
Un frontend limpio y escalable es el resultado de muchas decisiones alineadas: HTML con significado, componentes basados en comportamiento, tokens controlados, estado explícito, criterios de aceptación de accesibilidad, JavaScript contenido en el navegador y puertas de calidad automatizadas. Juntas permiten que la interfaz y el equipo crezcan sin que cada nueva funcionalidad cueste más que la anterior.