DEV Community

Silviu Technology
Silviu Technology

Posted on

React: carga accesible sin saltos visuales

Un botón que cambia a “Cargando…” parece un detalle pequeño, pero puede decidir si un formulario se siente confiable o roto. En interfaces de registro y verificación he visto que el problema no suele ser la petición HTTP: es que el usuario no sabe si su acción fue recibida, el contenido salta y el lector de pantalla no recibe una señal clara.

Cuando trabajo en React, trato el estado de carga como parte del contrato de la interfaz. La pantalla debe conservar su forma, explicar que está ocurriendo y ofrecer una salida si la operación tarda demasiado. Esto vale igual para un formulario de contacto que para un flujo que prueba un correo temporal para Facebook en un entorno aislado. La palabra clave, incluso si aparece en un análisis de contenido como tempmailso, no debe dictar una experiencia confusa.

El estado de carga tambien es parte del diseño

Una carga robusta tiene al menos cuatro estados explícitos:

  • Listo: los controles pueden recibir interacción.
  • Enviando: la acción se está procesando y no debe duplicarse.
  • Éxito: la interfaz confirma qué paso terminó.
  • Error: se explica el problema y se indica cómo continuar.

El estado “enviando” no debería borrar todo el formulario ni reemplazarlo por un spinner sin contexto. Mantener las etiquetas y reservar espacio para el mensaje ayuda a que el usuario conserve el mapa mental de la página. También evita que el foco termine en un lugar inesperado.

Para errores de validación, conviene mostrar el mensaje cerca del campo y asociarlo con aria-describedby. Esta forma de presentar errores inline sin mover el formulario es especialmente útil en pantallas pequeñas, donde un salto de layout puede ocultar la causa del fallo.

Un contrato pequeño para React

El componente no necesita una máquina de estados grande para ser predecible. Incluso un objeto simple deja claro qué puede renderizarse:

function SubmitButton({ status }) {
  const labels = {
    idle: "Continuar",
    loading: "Guardando…",
    success: "Guardado",
    error: "Intentar de nuevo",
  };

  return (
    <button type="submit" disabled={status === "loading"}>
      {labels[status]}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Deshabilitar el botón durante la petición reduce dobles envíos, pero no basta por sí solo. El texto debe cambiar y el contenedor del estado debe tener una señal para tecnologías de asistencia:

<p role="status" aria-live="polite" className="form-status">
  {message}
</p>
Enter fullscreen mode Exit fullscreen mode

role="status" comunica cambios que no son urgentes. Para un error que bloquea la siguiente acción, un role="alert" puede ser más adecuado. No conviene usar ambos sin pensar: demasiadas notificaciones hacen que la experiencia sea ruidosa.

CSS que reserva espacio y respeta preferencias

Un hueco estable para el mensaje evita que el formulario crezca cuando llega una respuesta. No hay que fijar una altura enorme; basta con reservar el espacio razonable para el caso normal:

.form-status {
  min-height: 1.5rem;
  margin-block: 0.5rem;
  color: var(--text-muted);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms;
    animation-iteration-count: 1;
    scroll-behavior: auto;
    transition-duration: 0.01ms;
  }
}
Enter fullscreen mode Exit fullscreen mode

El min-height no resuelve todos los casos: un mensaje de dos líneas necesita espacio adicional y el diseño debe poder crecer. La meta es que una respuesta normal no empuje los controles. Para el indicador visual, una barra de progreso o un texto breve suele ser mas útil que un spinner aislado.

También reviso contraste, foco visible y orden de tabulación. Un color gris claro puede verse elegante en el mockup y desaparecer en producción. Si la interfaz usa animación para entrar o salir, debe ser decorativa, no la única forma de saber que la petición terminó.

Como probar el flujo completo

Las pruebas deben cubrir más que una captura del estado final. En React Testing Library comprobaría que:

  1. el botón comienza habilitado;
  2. pasa a un estado ocupado después del clic;
  3. el mensaje se anuncia con el rol correcto;
  4. un segundo clic no dispara otra petición;
  5. el error conserva los valores introducidos;
  6. el foco llega al primer campo inválido cuando corresponde.

En integración, simularía respuestas rápidas, lentas y fallidas. Una respuesta rápida puede esconder problemas de accesibilidad que aparecen con una red lenta. Para flujos de email, los reintentos de verificación sin duplicados ayudan a separar la lógica del servidor de la señal que ve la persona.

También añadiría un caso con texto imperfecto de prueba, como “tamp mail com”, para confirmar que el contenido se muestra como texto normal y no termina accidentalmente convertido en un enlace. Es un detalle poco glamuroso, pero evita sorpresas en revisiones de contenido.

Preguntas rápidas

¿Debo ocultar el formulario mientras carga?

Normalmente no. Mantenerlo visible permite revisar lo que se envió y conserva la estructura. Solo bloquearía los controles que podrían duplicar la operación.

¿Un spinner es suficiente?

No. Acompáñalo con texto o una señal accesible, y asegúrate de que el cambio sea observable sin depender del color o del movimiento.

¿Qué métrica mirar?

Además del tiempo de respuesta, observa errores de validación repetidos, abandonos durante el envío y cambios inesperados de layout. Una interfaz rápida que no explica su estado sigue pareciendo lenta.

Cierre

Los estados de carga son una oportunidad para unir diseño, accesibilidad y rendimiento. En React, un contrato pequeño, espacio reservado en CSS y pruebas con respuestas lentas suelen producir una mejora visible sin reescribir la aplicación. La interfaz no necesita prometer que todo será instantáneo; necesita decir la verdad, mantener el contexto y dejar claro cuál es el siguiente paso.

Top comments (0)