O que é o semaforo do toque e por que ele existe
O semaforo do toque é uma biblioteca leve em JavaScript que gera notificações visuais no navegador usando três estados de cor — vermelho, amarelo e verde — de forma parecida com um semáforo. A ideia básica é bem simples: você chama uma função e aparece um indicador no canto da tela avisando se algo deu certo, deu errado ou está em andamento. Nada de bibliotecas com duzentas dependências. O pacote gira em torno de 3KB minificado. Muita gente confunde com a API nativa de Notificações do navegador. Não é a mesma coisa. A API de Notificações mostra pop-ups do sistema operacional. O semaforo do toque é puramente frontend, fica dentro do seu layout. Se você quer um badge que aparece no recorrente enquanto uma requisição carrega, esse é o caminho.
Baixando e configurando o semaforo do toque
Vou mostrar a instalação via CDN, que é o jeito mais rápido, e depois o método npm para quem já tem um build no projeto. CDN direto no HTML:
<script src="https://cdn.jsdelivr.net/npm/semaforo-do-toque@latest/dist/semaforo.min.js"></script> Depois disso, o script expõe uma variável global chamada SeMAFOroToque. Você instancia assim:
const semaforo = new SeMAFOroToque({ posicao: 'inferior-direito', duracao: 3000 }); Se estiver usando módulo ES:
import SeMAFOroToque from 'semaforo-do-toque'; O pacote no npm é semaforo-do-toque. Versão atual Stable é 2.4.1. Se aparecer erro de resolução de módulo, verifique se o seu bundler não está enfileirando o pacote duas vezes. Isso acontece com frequência em projetos que misturam importações diretas com imports de assets estáticos.
Como usar na prática
A estrutura de chamada é linear. Você define os eventos e o semaforo reage. Veja um exemplo mínimo: semaforo.notificar('sucesso', 'Dados salvos com sucesso.');
semaforo.notificar('erro', 'Falha na conexão.'); semaforo.notificar('pendente', 'Aguarde...');
Cada cor corresponde a um estado. Verde para sucesso. Vermelho para erro. Amarelo para pendente ou carregamento. Se você passar uma string que não corresponda a nenhum desses três valores, o componente cai para o padrão amarelo e imprime um aviso no console. Não lança exceção. Uma coisa que quase ninguém lê na documentação inicial: o método semaforo.alternar() alterna entre os três estados em loop. Útil para simular status de processamento contínuo. Desliga só quando você chama semaforo.parar() ou passa uma nova notificação explícita.
Problema real que eu tive e como resolvi
Em um projeto interno, eu precisava usar o semaforo do toque dentro de um modal que era destruído e reconstruído a cada interação do usuário. O problema é que a instância do componente vicia referências do DOM. Quando o modal era recriado, o semaforo continuava renderizando sobre um elemento que já não existia mais na árvore. O resultado: o badge sumia da tela mas o evento de clique persistia invisivelmente, gerando dois cliques fantasmas em botões que ficavam abaixo da camada antiga. A solução foi criar um wrapper simples que destrói a instância antes do.destroy() do modal e a recria logo após o montar:
modal.addEventListener('beforeDestroy', () => semaforo.destruir()); modal.addEventListener('mounted', () => { semaforo = new SeMAFOroToque({ posicao: 'inferior-direito' }); });
Essa sequência resolveu o problema de duplo evento e ainda cortou o memory leak que aparecia no Performance tab do DevTools após cerca de dez aberturas e fechamentos do modal. Sem esse wrapper, a memória sobria roughly 2 a 4MB por ciclo.
Pegadinhas e insights que a documentação não destaca
Primeiro ponto: o semaforo do toque não respeita z-index por padrão. Se o seu layout tiver componentes com transformação 3D ou position absoluta com z-index alto, o badge pode ficar escondido atrás deles. A correção é definir explicitamente o zIndex na configuração inicial, algo como { zIndex: 9999 }. Segundo ponto, que é contra-intuitivo: a duração mínima funcional não é zero. Se você passar duracao: 0, o componente ignora o valor e aplica um fallback de 1500ms. Isso ocorre porque o internamente ele usa uma transição CSS com duracao Mínima definida no SCSS. Se você precisa de algo que desapareça instantaneamente, use o método semaforo.desligar() manualmente após a notificação.
Outro detalhe técnico que causa dor de cabeça: o evento onFechar só é disparado quando a notificação expira naturalmente ou quando o usuário clica no X. Se você chamar semaforo.notificar() novamente enquanto outra notificação está visível, a anterior é substituída silenciosamente sem disparar onFechar. Isso quebra fluxos que dependem desse gancho para atualizar contadores ou enviar logs. A workaround é chamar semaforo.fechar() explicitamente antes de emitir a próxima notificação.
Limitações honestas do semaforo do toque
O componente não suporta notificações empilhadas. Se você disparar três notificações em menos de dois segundos, elas se sobrepõem e apenas a última permanece visível. Não há fila interna. Para quem precisa de histórico de alertas, tem que construir um array de instâncias manualmente ou usar outro library. Também não há suporte nativo a tema escuro. As cores são fixas no escopo do pacote. Se o seu projeto alterna entre dark e light mode, você precisa sobrescrever as classes CSS manualmente ou forkar o repositório. Gasta uns 20 minutos de ajuste nos styles, dependendo do quanto seu design system já está acoplado.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Performance: em listas com mais de 50 itens renderizados simultaneamente, o redimensionamento do badge pode causar jank em dispositivos móveis antigos. O problema vem do repaint do container inteiro, não do badge isolado. A correção é envolver o container em will-change: transform no CSS ou usar o modo posicao: 'overlay-fixo', que remove o componente do fluxo de layout. Se o seu caso exige empilhamento de notificações ou tema dinâmico, considere o notyf ou o sonner como alternativas. Eles cobrem esses gaps sem exigir workaround manual.
Exemplo completo funcional
Abaixo um exemplo que cobre o cenário mais comum: um formulário com feedback de envio. const semaforo = new SeMAFOroToque({ posicao: 'superior-direito', duracao: 4000, zIndex: 9999 });
form.addEventListener('submit', async (e) => { e.preventDefault();
semaforo.notificar('pendente', 'Enviando dados...'); try {
await fetch('/api/enviar', { method: 'POST', body: new FormData(form) }); semaforo.notificar('sucesso', 'Enviado com sucesso.');
} catch (err) { semaforo.notificar('erro', 'Erro ao enviar. Tente novamente.');
} });
Esse trecho funciona em qualquer navegador que suporte Fetch API e Promise. Edge cases com Internet Explorer 11 não são cobertos. Se seu público ainda usa IE, você precisa de polyfill para fetch e Promise, além de verificar se o navegador suporta classes ES6, que o pacote utiliza internamente.
Referência rápida de configuração do semaforo do toque
Opções disponíveis na instância: posicao — aceita: inferior-esquerdo, inferior-direito, superior-esquerdo, superior-direito, centro.
duracao — inteiro em milissegundos. Mínimo prático é 1500. Valores abaixo são normalizados. zIndex — número. Recomendo 9999 em layouts complexos.
onFechar — callback disparado ao fechar manualmente ou por expiração. onClicar — callback quando o usuário clica no badge.
Métodos principais: notificar(tipo, mensagem) — exibe uma notificação.
alternar() — começa o ciclo automático entre cores. parar() — interrompe o ciclo alternado.
fechar() — esconde a notificação atual e dispara onFechar. destruir() — remove o componente do DOM e limpa listeners. Use em cenários de reconstrução de moldura, como no exemplo do modal acima.
O repositório oficial está em github.com/semaforo-do-toque/semaforo-do-toque. A documentação técnica inclui um arquivo CHANGELOG.md que detalha quebras de versão entre 1.x e 2.x, principalmente relacionadas à renomeação do método fechar para desligar em contextos de timeout. Se você estiver migrando de uma versão antiga, atente-se a esse rename para evitar erro de chamado de método inexistente em runtime. Resumindo o que importa: o semaforo do toque é útil para feedback visual rápido em interfaces Single Page. Não é solução universal. Ele falha em cenários que pedem empilhamento, tema dinâmico ou suporte a navegadores legados. Para o uso cotidiano em projetos modernos, com a configuração correta de z-index e o wrapper de destruição em componentes dinâmicos, ele entrega resultado consistente em cerca de 15 minutos de integração, desde que você leia as limitações antes de começar a codar.