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
- Cada campo tem um elemento de erro com
idpróprio, ligado ao campo poraria-describedby. - Campo com erro recebe
aria-invalid="true"e um texto que diz como corrigir, não só que está errado. - No envio com erro, preencha um resumo no topo com links para cada campo e mova o foco para esse resumo.
- Use
role="alert"só no resumo, que é urgente. Mensagens de status usamrole="status", que é educado e não interrompe o leitor de tela. - 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?
Referência: https://www.w3.org/WAI/ARIA/apg/patterns/alert/