Programação Por Equipe DevBras

Capa do artigo: Toast que some sozinho: onde colocar o erro para ninguém perder

Toast que some sozinho: onde colocar o erro para ninguém perder

Use aviso flutuante só para o que é extra, deixe o erro preso ao campo e num resumo, configure aria-live do jeito certo e dê tempo de leitura a quem precisa.

O formulário de cadastro de produto salvou, mostrou "Salvo com sucesso" no canto inferior direito por dois segundos e apagou. A pessoa estava olhando o teclado. Ela clicou em salvar de novo, e agora o estoque tem dois "Café torrado 500 g" com o mesmo código interno.

A resposta curta: o aviso que some sozinho, o famoso toast, só pode carregar informação que também esteja em outro lugar. Sucesso aparece no estado da tela (o formulário fecha, o item surge na lista). Erro fica junto do campo e num resumo no topo, até ser corrigido. O toast é um reforço, não o registro.

Por que o aviso temporário falha

O toast parte de uma premissa falsa: que a pessoa está olhando para a tela, para aquele canto, naquele instante. Ela pode estar olhando o teclado, o papel com os dados, o outro monitor ou o filho. Pode ler devagar, pode usar ampliação de tela e ver só uma parte da página, pode usar leitor de tela.

Há também o problema do lugar. O canto inferior direito fica longe do campo e do botão. No celular, ele costuma cobrir justamente a barra de ação. E a cor sozinha (verde para sucesso, vermelho para erro) não informa nada a quem não distingue essas cores.

As diretrizes de acessibilidade do W3C (WCAG) tratam disso no critério de tempo ajustável: se o conteúdo tem limite de tempo, a pessoa precisa conseguir desligar, ajustar ou estender esse limite, salvo exceções. Um aviso que precisa ser lido e some em dois segundos não atende.

O que vai em cada lugar

Pense em três tipos de mensagem, cada um com sua casa.

Sucesso de ação simples

A própria tela mostra: o item aparece na lista, o botão "Salvar" vira "Salvo" com horário, o formulário fecha. O toast pode repetir, de forma breve. Se a tela não muda de um jeito visível, o toast não basta e você precisa de uma mudança persistente.

Erro de validação

Fica no campo, em texto, logo abaixo dele, e num resumo no topo do formulário com links para cada campo. Permanece até a pessoa corrigir. Nunca vai só para um toast, porque ela precisa olhar o campo enquanto lê a mensagem.

Erro de sistema

Rede caiu, servidor devolveu 500, sessão expirou. Aparece numa área fixa perto do botão de envio, com o que fazer: "Não foi possível salvar. Seus dados continuam no formulário. Tente de novo." E os dados digitados não somem.

Como montar o erro no formulário

  1. Cada campo tem um elemento de erro com id próprio, ligado ao campo por aria-describedby.
  2. Campo com erro recebe aria-invalid="true" e um texto que diz como corrigir, não só que está errado.
  3. No envio com erro, preencha um resumo no topo com links para cada campo e mova o foco para esse resumo.
  4. Use role="alert" só no resumo, que é urgente. Mensagens de status usam role="status", que é educado e não interrompe o leitor de tela.
  5. Indique erro com texto e ícone, não só com borda vermelha.
<div id="resumo-erros" role="alert" tabindex="-1" hidden>
  <p>Corrija 1 campo para salvar:</p>
  <ul>
    <li><a href="#codigo">Código interno: já existe um produto com este código</a></li>
  </ul>
</div>

<label for="codigo">Código interno</label>
<input id="codigo" name="codigo" aria-describedby="codigo-erro" aria-invalid="true">
<p id="codigo-erro">Já existe um produto com este código. Use outro ou edite o existente.</p>

O resumo começa com hidden e é revelado no envio com erro. Como as regiões com role="alert" são anunciadas quando o conteúdo muda, o resultado é mais confiável se o elemento já existir na página antes de você escrever nele. Por isso ele está no HTML desde o início, só escondido. O tabindex="-1" permite mover o foco para ele via script sem colocá-lo na ordem normal de Tab.

Se for usar toast, use direito

Para o reforço breve de sucesso, um toast bem feito tem quatro propriedades.

  • Uma região viva fixa. Crie o contêiner uma vez, com role="status", e só troque o texto dentro dele. Criar o elemento já com texto costuma não ser anunciado.
  • Tempo proporcional ao texto. Uma regra prática é partir de uns cinco segundos e somar tempo para mensagens longas. Pause o temporizador enquanto o mouse está em cima ou o foco está dentro.
  • Botão de fechar e, se houver ação, botão de ação. Toast com "Desfazer" precisa ser alcançável pelo teclado, e o desfazer precisa existir em outro lugar também, como mostra o post sobre desfazer em vez de confirmar.
  • Histórico. Se o sistema mostra muitos avisos, ofereça um lugar para reler os recentes, como um ícone de notificações.
const regiao = document.getElementById('avisos'); // <div id="avisos" role="status"></div>
let temporizador;

function mostrarAviso(texto) {
  regiao.textContent = texto;
  const tempo = 5000 + texto.length * 60;
  clearTimeout(temporizador);
  temporizador = setTimeout(() => { regiao.textContent = ''; }, tempo);
}

regiao.addEventListener('mouseenter', () => clearTimeout(temporizador));
regiao.addEventListener('mouseleave', () => mostrarAviso(regiao.textContent));

Para a frase "Produto Café torrado 500 g salvo." (33 caracteres), isso dá cerca de 7 segundos. Não é número mágico, é ponto de partida: teste com alguém lendo em voz alta e ajuste.

Evitando a duplicata do exemplo

O cadastro duplicado não é só problema de aviso. O botão "Salvar" deve ficar desabilitado e dizer "Salvando..." enquanto a requisição está em andamento, e o servidor deve recusar código interno repetido com um índice único. Mesmo com o aviso perfeito, o duplo clique acontece. O padrão para requisições repetidas está em idempotência no pagamento, e vale para qualquer formulário que cria registro.

Para notificações em tempo real, como "novo pedido chegou", a lógica é a mesma: o aviso é extra e o registro fica numa lista. O post notificação em tempo real aprofunda esse lado.

Erros comuns

  • Erro de validação só no toast. A pessoa não consegue ler a mensagem e olhar o campo ao mesmo tempo, e o aviso some antes de ela achar o problema.
  • Dois segundos de exibição. Quem lê devagar, usa ampliação ou estava olhando o teclado perde a mensagem.
  • Criar o contêiner do toast a cada aviso. Leitores de tela muitas vezes não anunciam a região que nasceu já preenchida.
  • role="alert" em tudo. Cada "Salvo" interrompe o que o leitor de tela estava falando e cansa quem depende dele.
  • Toast sobre o botão de ação no celular. A pessoa toca no aviso em vez do botão, ou espera ele sumir para continuar.
  • Só cor para diferenciar sucesso e erro. Quem não distingue verde de vermelho recebe a mesma mensagem nos dois casos.

Checklist

  • Todo sucesso relevante muda a tela de forma visível e persistente.
  • Erro de validação aparece em texto junto do campo, ligado por aria-describedby.
  • Envio com erro mostra resumo no topo com links e recebe o foco.
  • Erro de sistema preserva os dados digitados e diz o que fazer.
  • A região de avisos existe desde o carregamento da página.
  • O toast fica tempo proporcional ao texto e pausa com mouse ou foco.
  • Avisos com ação têm a mesma ação disponível em outro lugar.

Salve um formulário do seu sistema olhando para o teclado e só levante os olhos três segundos depois. Sem olhar para o canto da tela, dá para saber se salvou?

Avalie este artigo

Referência: https://www.w3.org/WAI/ARIA/apg/patterns/alert/