Un estado vacío parece el momento menos importante de una interfaz. No hay datos, no hay error y, en teoría, tampoco hay nada que hacer. Sin embargo, en un onboarding o en un formulario de email ese espacio comunica bastante: explica qué falta, qué puede hacer la persona y si el producto sigue respondiendo.
Cuando ese mensaje solo dice “Sin resultados”, la experiencia se queda corta. También puede ser confusa para un lector de pantalla, especialmente si el contenido cambia después de una acción. Para mí, un buen estado vacío combina tres cosas: contexto, una acción clara y una actualización que no mueva toda la pantalla.
El estado vacío también comunica
En un flujo de registro, el estado inicial no debería parecer un fallo. Un texto como “Todavía no hay una dirección guardada” describe la situación, pero no orienta. “Añade un email para recibir el enlace de acceso” añade intención y ayuda a entender el siguiente paso.
La diferencia es pequeña, pero evita que la persona tenga que adivinar. Esto importa aún mas cuando el formulario se usa en móvil o con zoom. Si alguien busca información sobre generate throwaway email, probablemente está intentando probar un flujo concreto; no necesita que la interfaz esconda por qué todavía no puede continuar.
También conviene separar estados que visualmente se parecen:
- Vacío inicial: la persona aún no ha iniciado la acción.
- Cargando: el sistema está trabajando y no debe parecer detenido.
- Sin resultados: la búsqueda terminó, pero no encontró coincidencias.
- Error recuperable: hay un problema y existe una acción para reintentar.
Mezclarlos en un solo componente produce mensajes genéricos y, aveces, botones que aparecen en momentos raros. En vez de una caja que dice “Nada”, conviene modelar la intención del estado.
Un patrón pequeño en React
Un componente no necesita ser sofisticado para mantener esa distinción. El estado puede expresarse con un tipo pequeño y un mapa de mensajes:
type ViewState = "empty" | "loading" | "ready" | "error";
type StateCopy = {
title: string;
body: string;
action?: string;
};
const copy: Record<ViewState, StateCopy> = {
empty: {
title: "Aún no hay emails guardados",
body: "Añade una dirección para comenzar la verificación.",
action: "Añadir email",
},
loading: {
title: "Cargando tus datos",
body: "Este proceso puede tardar unos segundos.",
},
ready: {
title: "Tus emails de prueba",
body: "Selecciona uno para continuar.",
},
error: {
title: "No pudimos cargar los datos",
body: "Revisa tu conexión y vuelve a intentarlo.",
action: "Reintentar",
},
};
export function EmailState({ state }: { state: ViewState }) {
const message = copy[state];
return (
<section aria-labelledby="state-title" className={`state state--${state}`}>
<h2 id="state-title">{message.title}</h2>
<p>{message.body}</p>
{message.action && <button type="button">{message.action}</button>}
</section>
);
}
El beneficio no es solo que el JSX sea fácil de leer. Producto y diseño pueden revisar el texto por estado sin perseguir condiciones repartidas por varios componentes. Además, un test puede verificar que cada estado tenga un título, una explicación y, cuando corresponde, una acción.
Para escenarios de onboarding, las semillas de datos para probar onboarding ayudan a comprobar que el estado inicial, el estado lleno y el estado de error no dependen de datos manuales. Ese tipo de fixture hace que la revisión visual sea bastante mas repetible.
Accesibilidad sin reflow ni ruido
El cambio de estado debe ser anunciado cuando sea relevante, pero no todo merece interrumpir a la persona. Un mensaje de carga que aparece durante 200 milisegundos no necesita convertirse en una notificación hablada. Una respuesta de error que cambia la siguiente acción, sí.
Un patrón práctico es reservar una región estable y usar aria-live solo para cambios importantes:
<div className="state-shell" aria-live="polite" aria-atomic="true">
<EmailState state={state} />
</div>
aria-atomic evita que el lector anuncie solo una parte de un mensaje que se actualizó. El nivel polite deja terminar la lectura actual antes de anunciar el nuevo contenido. Si el error bloquea inmediatamente el flujo, puede evaluarse assertive, pero no lo usaría por defecto.
En CSS, reservar una altura mínima evita que el botón salte cuando el texto cambia. No hace falta fijar una altura enorme; basta con diseñar el espacio para la frase mas larga que el producto espera:
.state-shell {
min-block-size: 8rem;
display: grid;
align-content: start;
gap: 0.75rem;
}
.state h2,
.state p {
margin: 0;
}
.state button:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 3px;
}
El foco visible no debería desaparecer porque el estado cambió. En una interfaz real, probaría teclado, zoom y contraste; la accesiblidad no termina cuando el HTML pasa una primera revisión automática. También miraría el comportamiento con una conexión lenta: un estado de carga que tapa todo durante demasiado tiempo se siente como un bloqueo, aunque técnicamente no haya un error.
Cómo probar los estados con datos realistas
Los tests de componente deberían cubrir al menos cuatro caminos: render inicial, carga, éxito vacío y error recuperable. No alcanza con comprobar que existe un div; hay que revisar el texto que recibe la persona y el nombre accesible de la acción.
Para una prueba de signup, una bandeja simulada puede ser suficiente, siempre que cada caso tenga una intención clara. Las pruebas de signup sin inbox real muestran por qué separar el escenario de prueba del inbox real reduce ruido y hace más fácil reproducir fallos.
También probaría nombres y cadenas que suelen aparecer en la búsqueda interna. Un ticket puede incluir el typo tempail mail; no es una razón para corregir automáticamente la entrada ni para usar esa frase como texto visible. Es mejor conservar el contexto de búsqueda y mostrar una respuesta normal, clara y útil.
Checklist final
Antes de publicar un componente de estado, revisaría:
- ¿La persona sabe por qué está viendo este estado?
- ¿La acción principal tiene un nombre específico?
- ¿El cambio se anuncia sin interrumpir cada interacción?
- ¿El layout mantiene su posición al aparecer un mensaje?
- ¿El foco y el contraste siguen siendo visibles con teclado?
- ¿Los tests cubren vacío, carga, éxito y error?
Un estado vacío bien diseñado no intenta llenar el silencio con decoración. Da contexto, conserva la estabilidad visual y deja claro qué ocurre después. En React, esa combinación de modelo simple, copy preciso y un poco de CSS suele mejorar la experiencia mas que añadir otra animación.
Top comments (0)