30 Reglas de UX en React
Reglas para construir interfaces React que se sientan rápidas, tolerantes e intuitivas. Cubre estados de carga, recuperación de errores, retroalimentación, accesibilidad y diseño de interacción.
Busca en todas las páginas de la documentación
Reglas para construir interfaces React que se sientan rápidas, tolerantes e intuitivas. Cubre estados de carga, recuperación de errores, retroalimentación, accesibilidad y diseño de interacción.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
1. Muestra algo inmediatamente. Nunca muestres una pantalla en blanco. Usa cargadores esqueléticos, contenido marcador de posición, o datos antiguos en caché mientras se cargan datos frescos.
<Suspense fallback={<ProductSkeleton />}>
<ProductDetails id={id} />
</Suspense>2. Usa actualizaciones optimistas para acciones iniciadas por el usuario. Cuando un usuario hace clic en "me gusta", "guardar" o "eliminar", actualiza la UI instantáneamente. Revierte si el servidor rechaza. Esperar al servidor hace que tu aplicación se sienta lenta.
const [optimisticLikes, addLike] = useOptimistic(
likes,
(state, newLike: Like) => [...state, newLike]
);3. Muestra estados pendientes en botones y formularios. Desactiva el botón de envío y muestra un spinner durante el envío. Los usuarios nunca deberían preguntarse si su clic se registró.
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button disabled={pending} type="submit">
{pending ? "Guardando..." : "Guardar"}
</button>
);
}4. Usa transiciones para actualizaciones no urgentes. Envuelve actualizaciones de estado costosas en useTransition para mantener la UI receptiva. El contenido anterior permanece interactivo mientras se renderiza el contenido nuevo.
const [isPending, startTransition] = useTransition();
function handleSearch(query: string) {
startTransition(() => {
setSearchResults(filterLargeList(query));
});
}5. Pre-carga rutas que el usuario es probable que visite. Next.js Link pre-carga por defecto en hover. Para navegación programática, usa router.prefetch("/target").
6. Transmite contenido progresivamente. No esperes a que todos los datos estén disponibles antes de mostrar algo. Usa múltiples límites de Suspense para que cada sección aparezca tan pronto como sus datos estén listos.
7. Debounce en entradas de búsqueda. Escribir dispara en cada pulsación de tecla. Usa debounce con 300ms de retraso para evitar solicitudes excesivas y cambios en la UI.
8. Evita cambios de diseño. Reserva espacio para imágenes (establece dimensiones), fuentes (usa next/font), y contenido dinámico (establece min-height). Los usuarios nunca deberían perder su posición de desplazamiento porque el contenido se movió.
9. Cachea agresivamente, invalida con precisión. Usa SWR, TanStack Query, o caché de Next.js para mostrar datos antiguos instantáneamente mientras se revalida en segundo plano. Los usuarios ven contenido inmediatamente.
10. Proporciona retroalimentación instantánea para cada interacción. Estados hover en botones, estados activos en enlaces, anillos de enfoque en entradas, destacados de selección en alternancias. Cada clic y hover debería producir retroalimentación visible.
11. Muestra errores en línea, no cajas de alerta. Muestra errores de validación junto al campo relevante. Nunca uses alert() o toast genérico para validación de formularios.
<div>
<label htmlFor="email">Email</label>
<input id="email" aria-invalid={!!errors.email} aria-describedby="email-error" />
{errors.email && (
<p id="email-error" className="text-sm text-red-600">{errors.email}</p>
)}
</div>12. Haz que los errores sean recuperables. Cada estado de error debería incluir una forma de avanzar: un botón de reintento, un enlace para volver, o una sugerencia de qué probar a continuación.
13. Preserva la entrada del usuario en errores. Si el envío del formulario falla, nunca borres el formulario. Mantén todos los datos ingresados y resalta solo los campos que necesitan corrección.
14. Usa notificaciones toast para operaciones en segundo plano. Las confirmaciones de guardado, finalizaciones de tareas asincrónicas, y errores sin bloqueo pertenecen a los toasts. Mantenlos breves (menos de 5 segundos) con una acción cuando sea relevante.
15. Maneja estados vacíos cuidadosamente. Una lista vacía no es un error. Muestra un mensaje útil con un llamado a la acción.
{items.length === 0 ? (
<div className="text-center py-12">
<p className="text-muted-foreground">Sin proyectos todavía</p>
<Button onClick={onCreate}>Crea tu primer proyecto</Button>
</div>
) : (
<ProjectList items={items} />
)}16. Muestra confirmación para acciones destructivas. Eliminar, cancelar suscripción, remover miembro del equipo. Estas necesitan un diálogo de confirmación con consecuencias claras establecidas.
17. Proporciona deshacer en lugar de confirmación cuando sea posible. "Mensaje eliminado. Deshacer" es mejor UX que "¿Estás seguro de que quieres eliminar?" El usuario puede proceder más rápido, y los errores son recuperables.
18. Maneja el estado sin conexión correctamente. Detecta estado sin conexión con navigator.onLine y los eventos online/offline. Muestra un banner, colas de mutaciones, y sincroniza cuando se reconecta.
19. Usa HTML semántico primero. button para acciones, a para navegación, nav para navegación, main para contenido principal, h1-h6 en orden. El HTML semántico es accesible por defecto.
20. Todo elemento interactivo debe ser accesible por teclado. El orden de tabulación debería ser lógico. Enter/Space activan botones. Escape cierra modales. Teclas de flecha navegan menús. Prueba sin un ratón.
21. Toda imagen necesita un atributo alt. Alt descriptivo para imágenes de contenido. alt="" vacío para imágenes decorativas. Nunca omitas el atributo.
| Tipo de Imagen | Texto Alt |
|---|---|
| Contenido (foto, gráfico) | Describe qué muestra la imagen |
| Decorativa (fondo, divisor) | alt="" (cadena vacía) |
| Funcional (botón de icono) | Describe la acción: "Cerrar menú" |
22. El color solo no debe transmitir significado. Los estados de error necesitan iconos o texto además del color rojo. Los estados de éxito necesitan más que verde. Considera usuarios daltónicos (8% de los hombres).
23. Maneja el enfoque en cambios de ruta y modales. Cuando un modal se abre, enfoca el primer elemento enfocable dentro. Cuando se cierra, devuelve el enfoque al disparador. En navegación del lado del cliente, enfoca el contenido principal o el encabezado.
24. Anuncia cambios de contenido dinámico. Usa aria-live="polite" para actualizaciones de estado (notificaciones de toast, conteos de resultados de búsqueda) para que los lectores de pantalla los anuncien.
<div aria-live="polite" aria-atomic="true">
{results.length} resultados encontrados
</div>25. Etiqueta todos los controles de formulario. Cada entrada necesita una label visible o aria-label. El texto de marcador de posición no es una etiqueta. Asocia etiquetas con htmlFor coincidiendo con el id de entrada.
26. Haz las áreas clicables lo suficientemente grandes. Los objetivos táctiles deberían ser al menos 44x44px (WCAG). Botones y enlaces pequeños frustran a los usuarios móviles.
// Bien: botón con padding
<button className="px-4 py-3 min-h-[44px]">Guardar</button>
// Mal: objetivo diminuto
<button className="px-1 py-0.5 text-xs">x</button>27. Muestra estados de carga en contexto. Un spinner dentro del botón que se hizo clic es mejor que una pantalla de carga de página completa. Muestra progreso donde el usuario está mirando.
28. Usa divulgación progresiva. No abrumarás a los usuarios con cada opción a la vez. Muestra lo esencial primero, revela opciones avanzadas bajo demanda (acordeón, "Mostrar más", pestañas).
<div>
<BasicSettings />
<details>
<summary className="cursor-pointer text-sm text-blue-600">
Configuración avanzada
</summary>
<AdvancedSettings />
</details>
</div>29. Respeta las preferencias del usuario. Honra prefers-reduced-motion para animaciones, prefers-color-scheme para modo oscuro, y prefers-contrast para alto contraste. Estas son configuraciones de accesibilidad a nivel del sistema.
// CSS
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}// React hook
const prefersReducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");30. Sé consistente. Las mismas acciones deberían verse igual en todas partes. Si "Guardar" es un botón azul en una página, debería ser un botón azul en cada página. Ubicación consistente, estilo consistente, comportamiento consistente. La consistencia reduce la carga cognitiva.
useOptimistic muestra un resultado inmediato, asumido como exitoso mientras se ejecuta una Server Action (revierte en caso de fallo)useTransition mantiene la UI actual interactiva mientras se renderiza una actualización de estado no urgente en segundo planouseOptimistic para mutaciones (like, save); usa useTransition para filtrado costoso o navegaciónfunction SubmitButton() {
const { pending } = useFormStatus();
return (
<button disabled={pending} type="submit">
{pending ? "Guardando..." : "Guardar"}
</button>
);
}useFormStatus lee el estado pendiente del <form> padre más cercano.
alert() bloquea el hilo y no proporciona contexto sobre qué campo fallóaria-describedby para accesibilidad de lector de pantallapx-4 py-3 min-h-[44px]) incluso en botones pequeños<div aria-live="polite" aria-atomic="true">
{results.length} resultados encontrados
</div>Usa aria-live="polite" para actualizaciones no urgentes (conteos de búsqueda, toasts) para que el lector de pantalla los anuncie después del habla actual.
<label> visible con htmlFor o aria-label en la entradafunction useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState<boolean>(false);
useEffect(() => {
const mql = window.matchMedia(query);
setMatches(mql.matches);
const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
mql.addEventListener("change", handler);
return () => mql.removeEventListener("change", handler);
}, [query]);
return matches;
}type ErrorFallbackProps = {
error: Error;
resetErrorBoundary: () => void;
};
function ErrorFallback({ error, resetErrorBoundary }: ErrorFallbackProps) {
return (
<div role="alert">
<p>Algo salió mal: {error.message}</p>
<button onClick={resetErrorBoundary}>Intentar de nuevo</button>
</div>
);
}prefers-reduced-motion: reduce -- desactiva o minimiza animacionesprefers-color-scheme: dark -- ofrece modo oscuroprefers-contrast: more -- aumenta contraste para usuarios de baja visión| Categoría | Regla Clave |
|---|---|
| Velocidad | Muestra algo inmediatamente. Actualizaciones optimistas. Transmite progresivamente. |
| Errores | Errores en línea. Preserva entrada. Siempre ofrece recuperación. |
| Accesibilidad | HTML semántico. Accesible por teclado. Gestión del enfoque. |
| Retroalimentación | Estados pendientes en botones. Toast para operaciones en segundo plano. Deshacer sobre confirmar. |
| Toque | Objetivos mínimos de 44px. Carga consciente del contexto. Divulgación progresiva. |
Revisado por Chris St. John·Última actualización: 7 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥