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
Defina o escopo do loading: tela inteira (primeiro carregamento) ou parcial (apenas uma lista, apenas um card, apenas um botão).
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!
Baixar o aplicativo
Garanta continuidade visual: o skeleton deve ter a mesma “silhueta” do conteúdo real (tamanho de avatar, linhas de texto, cards).
Inclua acessibilidade: descreva o estado para leitores de tela (ex.: “Carregando resultados”).
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)
| Contexto | Mensagem neutra e clara |
|---|---|
| Carregamento de lista | Carregando itens… |
| Busca | Buscando resultados… |
| Envio de dados | Enviando… |
| Processamento demorado | Isso 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
Identifique a causa mais provável: primeiro uso, filtro, falta de permissão, etc.
Escreva a mensagem em linguagem do usuário: sem termos técnicos (“endpoint”, “payload”, “status 204”).
Defina uma ação principal: algo que resolva o vazio em um toque.
Se houver filtros: inclua “Limpar filtros” ou “Ver todos”.
Evite becos sem saída: sempre ofereça um caminho (criar, explorar, ajustar).
Modelos de mensagens (empty)
| Cenário | Título | Texto | Ação principal | Ação secundária |
|---|---|---|---|---|
| Primeiro uso | Nenhum item ainda | Adicione seu primeiro item para começar. | Adicionar item | Ver exemplo |
| Busca sem resultados | Nada encontrado | Tente outro termo ou ajuste os filtros. | Limpar filtros | Ver todos |
| Lista vazia por configuração | Sem itens nesta categoria | Escolha outra categoria para ver mais opções. | Ver categorias | Ver 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
Classifique o erro: rede, servidor, validação, permissão, não encontrado.
Defina o nível: erro de tela inteira (não há conteúdo) vs erro localizado (apenas um componente falhou).
Escreva a mensagem: o que aconteceu + o que fazer agora.
Escolha ações: primária (retry/ajustar) e secundária (voltar/contato).
Inclua “Detalhes” somente se necessário: um identificador curto para suporte (ex.: “ID do erro: A1B2”).
Modelos de mensagens (erro)
| Tipo | Mensagem | Ação principal | Ação secundária |
|---|---|---|---|
| Rede | Não foi possível conectar. Verifique sua internet e tente novamente. | Tentar novamente | Voltar |
| Servidor | Algo não funcionou como esperado. Tente novamente em instantes. | Tentar novamente | Ver detalhes |
| Permissão | Para continuar, precisamos de acesso a este recurso. | Permitir acesso | Agora não |
| Validação (form) | Revise os campos destacados e tente novamente. | Corrigir | Cancelar |
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ário | Mensagem | Próximo passo |
|---|---|---|
| Salvar configurações | Alterações salvas. | Voltar |
| Criar item | Item criado com sucesso. | Ver item |
| Enviar formulário | Recebemos suas informações. | Acompanhar status |
| Atualizar lista | Atualizado. | — |
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
spinnereskeleton, 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
Crie um vocabulário de estados: defina nomes e quando usar (loading inicial, refresh, erro de rede, erro de permissão, vazio por filtro).
Defina propriedades obrigatórias: por exemplo, todo erro de tela inteira deve ter título + descrição + ação primária.
Centralize textos padrão: mantenha modelos de mensagens em um único lugar para consistência e manutenção.
Padronize ações: “Tentar novamente” sempre faz a mesma coisa (refetch), “Limpar filtros” sempre reseta filtros.
Documente exemplos de uso: uma tabela simples “estado → componente → quando usar”.
Tabela de mapeamento: estado → componente → comportamento
| Estado | Componente | Comportamento esperado |
|---|---|---|
| Loading inicial | LoadingState (skeleton) | Bloqueia conteúdo e mostra estrutura da tela |
| Refresh | LoadingState (spinner pequeno) | Não apaga conteúdo; indica atualização |
| Empty (primeiro uso) | EmptyState | Explica e oferece CTA de criação |
| Empty (filtro) | EmptyState | Oferece “Limpar filtros” e alternativa |
| Erro de rede | ErrorState | Mostra retry e mantém navegação possível |
| Sucesso de ação | SuccessFeedback | Confirma 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.