Estados de tela no app: carregando, vazio, erro e sucesso

Capítulo 10

Tempo estimado de leitura: 9 minutos

+ Exercício

Por que estados de tela fazem parte do design (e não são exceção)

Em um app real, a interface quase nunca está “pronta” o tempo todo. Dados demoram para carregar, listas podem vir vazias, conexões falham e ações precisam de confirmação. Esses momentos são previsíveis e recorrentes, então devem ser projetados com o mesmo cuidado que a tela “ideal”.

Tratar estados como parte do design traz três ganhos práticos: (1) reduz frustração porque o usuário entende o que está acontecendo, (2) diminui abandono porque sempre existe um próximo passo claro, (3) facilita o desenvolvimento porque você padroniza componentes reutilizáveis e evita soluções improvisadas por tela.

Estrutura por tipo de estado

1) Loading (carregando): feedback, tempo e escolha entre skeleton e spinner

Objetivo do loading: deixar claro que o app está trabalhando e evitar a sensação de travamento. Um bom loading responde a três perguntas: “está carregando?”, “o que está carregando?” e “vai demorar?”.

Spinner vs Skeleton: quando usar

  • Spinner (indicador indeterminado): melhor para ações curtas e simples, quando você não consegue prever o layout final ou quando o conteúdo não tem estrutura fixa (ex.: validação rápida, envio de formulário, autenticação).
  • Skeleton (placeholder estruturado): melhor quando a tela final tem layout previsível (lista de cards, perfil, feed). Ele reduz a percepção de espera porque “antecipa” a estrutura do conteúdo.

Regras práticas de tempo

  • < 300 ms: muitas vezes não vale mostrar nada (evita “piscar”). Se necessário, use um atraso intencional antes de exibir o loading.
  • 300 ms a 2 s: spinner ou skeleton funcionam bem. Prefira skeleton em telas de conteúdo.
  • > 2 s: adicione mensagem curta e, se fizer sentido, opção de cancelar/voltar. Se houver etapas (upload, processamento), use progresso determinado.

Passo a passo: projetando um estado de loading reutilizável

  1. Defina o escopo do loading: tela inteira (primeiro carregamento) ou parcial (apenas uma lista, apenas um card, apenas um botão).

  2. Escolha o padrão: skeleton para conteúdo estruturado; spinner para ações rápidas/indeterminadas.

    Continue em nosso aplicativo e ...
    • Ouça o áudio com a tela desligada
    • Ganhe Certificado após a conclusão
    • + de 5000 cursos para você explorar!
    ou continue lendo abaixo...
    Download App

    Baixar o aplicativo

  3. Garanta continuidade visual: o skeleton deve ter a mesma “silhueta” do conteúdo real (tamanho de avatar, linhas de texto, cards).

  4. Inclua acessibilidade: descreva o estado para leitores de tela (ex.: “Carregando resultados”).

  5. Defina timeout e fallback: após um tempo limite, transicione para estado de erro com retry, em vez de ficar carregando indefinidamente.

Modelos de mensagens (loading)

ContextoMensagem neutra e clara
Carregamento de listaCarregando itens…
BuscaBuscando resultados…
Envio de dadosEnviando…
Processamento demoradoIsso pode levar alguns segundos…

2) Empty state (vazio): orientação e próxima ação

Objetivo do vazio: explicar por que não há conteúdo e orientar o que fazer agora. “Vazio” não é erro; é uma condição válida do sistema (primeiro uso, filtros restritivos, nenhuma mensagem, nenhuma compra, etc.).

Tipos comuns de vazio

  • Primeiro uso (onboarding contextual): ainda não existe conteúdo porque o usuário não criou nada.
  • Resultado vazio: existe conteúdo no sistema, mas a busca/filtro não encontrou nada.
  • Permissão/integração ausente: falta uma etapa para o conteúdo aparecer (ex.: permitir notificações, conectar conta).

Checklist do empty state

  • Título curto: descreve o estado (“Nenhum item por aqui”).
  • Explicação em 1 frase: causa provável (“Você ainda não adicionou itens”).
  • Ação principal: botão com verbo (“Adicionar item”, “Criar projeto”, “Limpar filtros”).
  • Ação secundária (opcional): link para aprender/ajuda (“Como funciona”, “Ver exemplos”).
  • Se for resultado vazio: ofereça ajuste rápido (limpar filtros, alterar termo, ver tudo).

Passo a passo: desenhando um empty state que converte

  1. Identifique a causa mais provável: primeiro uso, filtro, falta de permissão, etc.

  2. Escreva a mensagem em linguagem do usuário: sem termos técnicos (“endpoint”, “payload”, “status 204”).

  3. Defina uma ação principal: algo que resolva o vazio em um toque.

  4. Se houver filtros: inclua “Limpar filtros” ou “Ver todos”.

  5. Evite becos sem saída: sempre ofereça um caminho (criar, explorar, ajustar).

Modelos de mensagens (empty)

CenárioTítuloTextoAção principalAção secundária
Primeiro usoNenhum item aindaAdicione seu primeiro item para começar.Adicionar itemVer exemplo
Busca sem resultadosNada encontradoTente outro termo ou ajuste os filtros.Limpar filtrosVer todos
Lista vazia por configuraçãoSem itens nesta categoriaEscolha outra categoria para ver mais opções.Ver categoriasVer todos

3) Erro: mensagem acionável, retry e linguagem humana

Objetivo do erro: informar o que falhou, reduzir ansiedade e oferecer um caminho de recuperação. Um erro bem projetado evita que o usuário fique preso e diminui chamados de suporte.

Princípios de mensagens de erro

  • Seja específico sem expor detalhes internos: “Não foi possível carregar seus pedidos” é melhor do que “Erro”.
  • Explique o impacto: o que não pôde ser feito agora.
  • Ofereça ação: “Tentar novamente”, “Verificar conexão”, “Editar dados”.
  • Use linguagem humana: evite códigos (500, 403) na interface. Se precisar, coloque o código em “Detalhes” para suporte.
  • Preserve o trabalho do usuário: em formulários, não apague campos; destaque o que precisa correção.

Retry: quando e como

  • Retry imediato: para falhas transitórias (timeout, rede instável). Botão “Tentar novamente”.
  • Retry automático com limite: útil em listas/feeds, com backoff e indicação discreta (“Reconectando…”). Sempre ofereça controle manual se persistir.
  • Alternativa ao retry: se a ação depende de pré-requisito (permissão, login), direcione para resolver a causa (“Fazer login”, “Permitir acesso”).

Passo a passo: criando um estado de erro consistente

  1. Classifique o erro: rede, servidor, validação, permissão, não encontrado.

  2. Defina o nível: erro de tela inteira (não há conteúdo) vs erro localizado (apenas um componente falhou).

  3. Escreva a mensagem: o que aconteceu + o que fazer agora.

  4. Escolha ações: primária (retry/ajustar) e secundária (voltar/contato).

  5. Inclua “Detalhes” somente se necessário: um identificador curto para suporte (ex.: “ID do erro: A1B2”).

Modelos de mensagens (erro)

TipoMensagemAção principalAção secundária
RedeNão foi possível conectar. Verifique sua internet e tente novamente.Tentar novamenteVoltar
ServidorAlgo não funcionou como esperado. Tente novamente em instantes.Tentar novamenteVer detalhes
PermissãoPara continuar, precisamos de acesso a este recurso.Permitir acessoAgora não
Validação (form)Revise os campos destacados e tente novamente.CorrigirCancelar

Códigos vs linguagem humana

Na interface, priorize linguagem humana. Se o time precisa de rastreabilidade, use um padrão como:

  • Texto para o usuário: “Não foi possível carregar seus dados.”
  • Detalhe opcional: “ID do erro: 7F3K” (copiável) em uma área secundária.

4) Sucesso: confirmação e próximo passo

Objetivo do sucesso: confirmar que a ação foi concluída e orientar o que acontece agora. Sem confirmação, o usuário pode repetir ações, duvidar do resultado ou abandonar o fluxo.

Formas comuns de feedback de sucesso

  • Inline: mensagem perto do elemento afetado (ex.: “Salvo” ao lado do campo).
  • Toast/snackbar: confirmações rápidas e não bloqueantes (ex.: “Item adicionado”).
  • Tela de sucesso: para ações importantes (ex.: envio de pedido, cadastro concluído).
  • Estado do botão: “Salvando…” → “Salvo” com ícone, por curto período.

Checklist do sucesso

  • Confirme o resultado: o que foi feito (“Pagamento confirmado”).
  • Mostre evidência quando útil: número do pedido, nome do item, data.
  • Indique o próximo passo: “Ver detalhes”, “Continuar”, “Compartilhar”, “Voltar para lista”.
  • Evite exageros: tom neutro e objetivo, sem mensagens longas.

Modelos de mensagens (sucesso)

CenárioMensagemPróximo passo
Salvar configuraçõesAlterações salvas.Voltar
Criar itemItem criado com sucesso.Ver item
Enviar formulárioRecebemos suas informações.Acompanhar status
Atualizar listaAtualizado.

Padronizando estados para reutilização no código

Para desenvolvedores, o ganho mais direto é transformar estados em componentes previsíveis. Em vez de cada tela “inventar” um loading/erro/vazio, você define um kit de estados com propriedades claras (título, descrição, ações, variação).

Modelo mental: State Container

Um padrão comum é um contêiner que decide o que renderizar com base em um estado de UI:

UIState = idle | loading | success(data) | empty | error(type, details)

Isso evita combinações incoerentes (ex.: mostrar lista vazia e spinner ao mesmo tempo) e facilita testes.

Componentes recomendados para um kit de estados

  • <LoadingState />: variações spinner e skeleton, com opção de escopo (full/section).
  • <EmptyState />: título, descrição, ação primária, ação secundária, ilustração opcional.
  • <ErrorState />: mensagem, ação “Tentar novamente”, ação alternativa, área opcional de detalhes/ID.
  • <SuccessFeedback />: inline/toast/tela, com mensagem curta e CTA.

Passo a passo: padronização mínima que funciona

  1. Crie um vocabulário de estados: defina nomes e quando usar (loading inicial, refresh, erro de rede, erro de permissão, vazio por filtro).

  2. Defina propriedades obrigatórias: por exemplo, todo erro de tela inteira deve ter título + descrição + ação primária.

  3. Centralize textos padrão: mantenha modelos de mensagens em um único lugar para consistência e manutenção.

  4. Padronize ações: “Tentar novamente” sempre faz a mesma coisa (refetch), “Limpar filtros” sempre reseta filtros.

  5. Documente exemplos de uso: uma tabela simples “estado → componente → quando usar”.

Tabela de mapeamento: estado → componente → comportamento

EstadoComponenteComportamento esperado
Loading inicialLoadingState (skeleton)Bloqueia conteúdo e mostra estrutura da tela
RefreshLoadingState (spinner pequeno)Não apaga conteúdo; indica atualização
Empty (primeiro uso)EmptyStateExplica e oferece CTA de criação
Empty (filtro)EmptyStateOferece “Limpar filtros” e alternativa
Erro de redeErrorStateMostra retry e mantém navegação possível
Sucesso de açãoSuccessFeedbackConfirma e sugere próximo passo

Exemplo de contrato de componente (agnóstico de framework)

EmptyStateProps: { title, description?, primaryAction {label, onPress}, secondaryAction? }
ErrorStateProps: { title, description, primaryAction, secondaryAction?, supportId? }
LoadingStateProps: { variant: 'spinner'|'skeleton', scope: 'full'|'section', label? }
SuccessFeedbackProps: { message, variant: 'inline'|'toast'|'screen', primaryAction? }

Com esse contrato, cada tela só decide qual estado está ativo; o “como parece e se comporta” fica padronizado e reutilizável.

Agora responda o exercício sobre o conteúdo:

Ao projetar um estado de loading para uma tela com layout previsível (como uma lista de cards), qual escolha está mais alinhada com as boas práticas e por quê?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Em telas com estrutura previsível, o skeleton mantém continuidade visual (mesma “silhueta” do conteúdo) e costuma diminuir a sensação de espera. O spinner é mais indicado para ações curtas/indeterminadas ou quando o layout final não é previsível.

Próximo capitúlo

Acessibilidade prática em design de interfaces para apps

Arrow Right Icon
Capa do Ebook gratuito Design de Interfaces para Apps: UI/UX essencial para desenvolvedores iniciantes
67%

Design de Interfaces para Apps: UI/UX essencial para desenvolvedores iniciantes

Novo curso

15 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.