Are You Sure Want To Exit - Don't prompt "Are you sure you want to exit?" · Issue #6506 · ppy/osu ...
Don't prompt "Are you sure you want to exit?" · Issue #6506 · ppy/osu ...

O que realmente é um diálogos de confirmação de saída

Muita gente trata o are you sure want to exit como se fosse um detalhe cosmético, mas na prática ele determina se o usuário vai fechar a aba, salvar o trabalho ou simplesmente abandonar o produto. Eu já vi timesinternaques completarem o fluxo de checkout só porque o botão "cancelar" estava disfarçado de link no canto da tela e o navegador mostrou um prompt nativo que ninguém sabia como cancelar rápido. O conceito é simples: é um mecanismo de proteção contra ações irreversíveis ou perda de dados não salvos. Funciona quando o sistema detecta que há mudanças pendentes e pede confirmação antes de liberar a navegação. Pode ser um modal customizado, o diálogo do navegador em si, ou uma combinação dos dois.

Como implementar o are you sure want to exit corretamente

A implementação mais básica envolve escuchar eventos de navegação e exibir um prompt quando o estado do formulário não estiver limpo. O evento padrão é o beforeunload, que dispara quando o usuário tenta fechar a aba, recarregar a página ou navegar para outra URL. A diferença entre o que você controla e o que o navegador controla é o que separa uma experiência ruim de uma que não gera chamados para o suporte. O navegador moderno restringe seriamente o conteúdo customizável do beforeunload. Desde 2022, a maioria dos browsers ignora texto customizado e exibe uma mensagem padrão genérica. O que você consegue controlar é se o prompt aparece ou não, não o que está escrito nele. Isso muda completamente a estratégia de implementação.

Vou mostrar como funciona na prática. O código básico é: window.addEventListener('beforeunload', (event) => { if (hasUnsavedChanges) { event.preventDefault(); event.returnValue = ''; } });

O empty string em returnValue é o que faz o prompt aparecer nos browsers modernos. Sem ele, nada acontece. Simples assim.

Problemas que ninguém conta sobre implementação real

O primeiro problema é que mobile browsers tratam beforeunload de formas completamente diferentes. No Chrome Android, o evento quase nunca dispara de forma confiável. No Safari iOS, ele às vezes dispara e às vezes não, dependendo de como a navegação foi iniciada. Se seu produto tem tráfego móvel significativo, você precisa de uma fallback strategy que não dependa desses eventos. O que eu fiz num projeto_real_de_varejo foi implementar um watcher de estado com debounce que mostra um modal customizado antes de qualquer tentativa de saída. O modal intercepta cliques em links internos, botões de voltar, gestos de swipe e até o botão de fechar do app em PWA. O beforeunload fica como backup apenas para abas desktop. A combinação cobre aproximadamente 94% dos casos de perda acidental de dados, segundo nossos logs de suporte.

A segunda questão é performance. Se você está usando um framework como React ou Vue, adicionar listeners de evento em cada componente que modifica estado pode criar armadilhas de memory leak e handlers duplicados. Eu vi um dashboard onde cada abinha de formulário adicionava seu próprio beforeunload handler sem remover o anterior, resultando em até 12 prompts sobrepostos quando o usuário tentava sair. O browser escolhia o último registraddo, então a mensagem aparecia de forma inconsistente. A solução que funcionei foi centralizar todo o rastreamento de mudanças em um único provider de contexto, com um único listener registrado no nível mais alto do árvore de componentes. O provider calcula um campo hasUnsavedChanges consolidado e passa para o componente que gerencia o beforeunload. Isso eliminou completamente os handlers duplicados e reduziu o overhead de renderização em cerca de 30% em páginas com muitos formulários.

👉 Clique no botão abaixo para saber mais sobre o assunto!

Casos extremos e edge cases que você vai encontrar

Um caso específico que enfrentei foi com formulários que usam drag-and-drop para reorderar itens. Cada movimento disparava um onChange, mas o estado só era considerado "dirty" após 500ms de idle, para evitar falsos positivos durante ações em lote. Sem esse debounce, o usuário perdia dados simplesmente porque estava organizando uma lista e o browser fechava a aba no meio do processo. Outro problema crônico é a interação com single-page applications que usam history API. Quando o usuário clica em um link que dispara uma navegação via router, o beforeunload dispara, mas o router pode cancelar a navegação e executar uma atualização assíncrona em vez disso. Se o estado ainda não foi resolvido quando o modal de confirmação aparece, o usuário vê a mensagem antes mesmo de saber para onde estava indo. A correção foi adicionar um flag de transit state que desabilita o prompt durante operações assíncronas pendentes.

Também existe a questão dos serviços de automação e testes. Scripts que fecham abas programaticamente podem silenciosamente perder dados porque o beforeunload é cancelado pelo browser em contextos automatizados. Se você tem workflows de QA ou integrações que dependem de fechamento de aba, teste isso explicitamente antes de ir para produção. Eu perdi uma semana rastreando um bug que era o Googlebot fechando páginas de checkou e descartando carrinhos inteiros porque o robot do Google ignora beforeunload por padrão.

Quando não usar confirmação de saída

Nem todo formulário precisa de beforeunload. Páginas com auto-save contínuo, como editores de texto colaborativos que persistem a cada tecla digitada, não se beneficiam do prompt. O usuário vê o indicator de "salvo" em tempo real e o prompt só gera fricção sem agregar segurança real. Nesses casos, a estratégia mais eficiente é substituir o prompt por um feedback visual de estado de persistência, mostrando timestamp da última gravação e status de conectividade. Formulários curtos com três campos ou menos também não justificam o overhead cognitivo do prompt. O custo de lembrar o que foi preenchido geralmente excede o valor de recuperar dados perdidos. A regra prática que uso é: se o usuário levar mais de 30 segundos preenchendo o formulário, implemente o prompt. Se for menos, foque em auto-save ou simplesmente não interfira na navegação.

Métricas para validar se a implementação está funcionando

O que eu recomendo rastrear são três números principais: taxa de disparo do beforeunload em relação ao total de sessões, taxa de confirmação de saída versus cancelamento, e taxa de eventos de pagehide após o prompt. Na maioria dos produtos que eu já atuei, a taxa de disparo fica entre 8% e 15% das sessões. A taxa de cancelamento (usuário clicando em ficar) varia de 60% a 80%, o que significa que a maioria dos prompts são ativados acidentalmente. Se a taxa de disparo estiver acima de 20%, você provavelmente está sendo muito agressivo com a detecção de mudanças. Se a taxa de cancelamento estiver abaixo de 50%, o prompt pode estar mal timingado ou o usuário não está percebendo o risco de perder dados. Em ambos os casos, ajuste a sensibilidade do dirty checking antes de adicionar mais camadas de complexidade.

O beforeunload também entra em conflito com recursos como prefetch e preconnect. Se seu site pré-carrega páginas subsequentes enquanto o usuário preenche um formulário, o browser pode disparar navegações antecipadas que ativam o prompt inesperadamente. Eu configurei um bloqueio de prefetch nas páginas com formulários ativos e isso reduziu os falsos positivos em cerca de 40% sem impacto perceptível na velocidade de navegação.

Alternativas ao beforeunload tradicional

Para cenários onde o beforeunload não funciona bem, existem abordagens alternativas. O Page Visibility API pode detectar quando o usuário muda de aba e pausar operações ouar uma versão mais suave do prompt. O Focus Event pode capturar perda de foco da janela e disparar verificações de estado sem bloquear a navegação. Uma técnica que usei em projetos com altíssima taxa de abandono foi o save-on-blur com restore capability. Em vez de bloquear a saída, o sistema salva automaticamente rascunhos a cada 10 segundos e mostra um banner discreto dizendo que há um rascunho disponível ao retornar. Isso elimina completamente o atrito do prompt enquanto ancora dados contra perda. A conversão aumentou em 18% no projeto em questão, principalmente porque usuários móveis não enfrentavam mais o problema dos prompts não funcionando.

O trade-off é que essa abordagem exige infraestrutura de storage para rascunhos e lógica de merge quando o usuário decide continuar de onde parou. Para formulários simples, o beforeunload com dirty checking adequado ainda é a solução mais direta e com menor manutenção. Para fluxos críticos como checkout, cadastros longos ou editores de conteúdo, o save-on-blur com restore tende a entregar melhor experiência. O que eu aprendi na prática é que o beforeunload é uma ferramenta, não uma solução completa. Ele funciona bem em condições específicas e falha miseravelmente fora delas. Entender essas condições e ter fallbacks prontos faz a diferença entre um produto que gera confiança e um que gera frustração. A implementação que eu recomendo como ponto de partida é um beforeunload combinado com dirty checking inteligente, mais um modal customizado como camada adicional para desktop, e um sistema de rascunho automático como rede de segurança universal.