Olha só: você provavelmente entendeu pointer-events errado
Quase todo dev conhece pointer-events: none do jeito errado: bateu um modal que tá engolindo os cliques da página, joga essa propriedade no overlay, resolve e segue a vida. Mas pouca gente entende o que ela faz de verdade.
Vamos lá com o modelo mental certo: pointer-events não desativa um elemento. Ele muda qual elemento se torna o alvo do evento durante o hit-testing.
Quando o navegador recebe um evento de ponteiro, ele faz um hit-test: desce a partir do elemento mais no topo sob o cursor até achar um que aceite eventos. Se o elemento do topo tem pointer-events: none, o browser pula ele e continua procurando embaixo. Só isso. É toda a propriedade.
Sacou isso, todo o resto — os valores SVG, a herança, o padrão de modal — encaixa. Se você curte esse tipo de análise de comportamento de baixo nível, dá uma olhada no nosso papo sobre sistemas de retrieval agêntico tipo Foundry IQ.

Sintaxe e valores principais
pointer-events: auto | bounding-box | visiblePainted | visibleFill | visibleStroke
| visible | painted | fill | stroke | all | none;
- Inicial:
auto - Herdada: sim
- Aplica-se a: todos os elementos
- Animação: discreta
Os dois valores que você vai usar 95% do tempo
/* Padrão: elemento se comporta normalmente e recebe eventos */
.interativo {
pointer-events: auto;
}
/* Elemento é ignorado no hit-test; eventos passam pra quem tá embaixo */
.overlay {
pointer-events: none;
}
Os nove valores exclusivos de SVG
/* Só dispara se visível E o ponteiro estiver sobre fill/stroke pintado */
.anel-visible-painted { pointer-events: visiblePainted; }
/* Dispara quando visível e sobre o fill, mesmo com fill: none */
.anel-visible-fill { pointer-events: visibleFill; }
/* Dispara quando visível e sobre o stroke, mesmo com stroke: none */
.anel-visible-stroke { pointer-events: visibleStroke; }
/* Visível + sobre fill OU stroke, ignorando valores de fill/stroke */
.anel-visible { pointer-events: visible; }
/* Só área pintada — ignora visibilidade completamente */
.anel-painted { pointer-events: painted; }
/* Só área de fill — ignora fill e visibilidade */
.anel-fill { pointer-events: fill; }
/* Só área de stroke — ignora stroke e visibilidade */
.anel-stroke { pointer-events: stroke; }
/* Caixa delimitadora inteira, mesmo regiões não pintadas */
.anel-bbox { pointer-events: bounding-box; }
/* Fill OU stroke, ignorando fill/stroke/visibility */
.anel-all { pointer-events: all; }
A pegadinha da herança (e como escapar dela)
pointer-events é herdada. Coloca none no pai e todo filho herda none. É exatamente por isso que o padrão de modal precisa de duas regras, não uma:
/* Container full-viewport que centraliza o modal */
.modal-backdrop {
pointer-events: none; /* deixa os cliques chegarem na página de trás */
}
/* O modal em si precisa "voltar" a receber eventos */
.modal-backdrop > .modal {
pointer-events: auto;
}
Esquece a segunda regra e seu modal fica completamente não-clicável — bug clássico que custa horas de debug.
O mesmo vale pra submenus invisíveis. Se você esconde um dropdown com opacity: 0, ele ainda tá no layout e ainda captura eventos. Adicionar pointer-events: none resolve o problema do "invisível mas clicável". É o mesmo tipo de problema de determinismo que a gente já cobriu no nosso deep dive sobre determinismo de ponto flutuante em reduções CUDA — mudanças pequenas e invisíveis que quebram tudo lá na frente.

O que pointer-events NÃO faz
Três equívocos clássicos pra desaprender:
1. Não para a propagação de eventos
pointer-events só afeta seleção de alvo. Depois que um elemento vira event.target, o evento segue os ciclos normais de captura e bubbling. Um pai com pointer-events: none ainda recebe click, pointerenter e pointerleave se o filho (com auto) for o alvo real.
// Esse listener DISPARA mesmo com o pai em pointer-events: none
pai.addEventListener('click', (e) => {
console.log('Borbulhou de:', e.target); // o filho
});
2. Não desativa um elemento
O elemento continua focável via Tab e continua respondendo ao teclado. Se você quer desabilitar um controle de formulário de verdade, usa o atributo disabled. Se quer remover uma subárvore inteira do input de ponteiro, do foco de teclado e da árvore de acessibilidade, usa o atributo inert.
<!-- Errado: o submit ainda dispara via teclado -->
<input type="submit" style="pointer-events: none">
<!-- Certo: realmente desabilitado -->
<input type="submit" disabled>
3. Não impede seleção de texto
O usuário ainda consegue selecionar texto dentro de um elemento com pointer-events: none usando Ctrl/Cmd + A. Seleção de texto é governada pela propriedade user-select, não pelo hit-testing.
.sem-selecao {
user-select: none;
}
Checklist prático
| Objetivo | Ferramenta correta |
|---|---|
| Deixar cliques passarem por overlay de modal | pointer-events: none no backdrop + auto no modal |
| Esconder submenu que não deve ser clicável | opacity: 0 + pointer-events: none |
| Desabilitar um botão | atributo disabled |
| Tornar uma seção inteira não-interativa | atributo inert |
| Impedir seleção de texto | user-select: none |
| Controlar qual região SVG é clicável | valores SVG de pointer-events |
Pegadinhas comuns
- Esquecer a herança:
noneno pai mata a interatividade de todos os filhos silenciosamente. - Usar
pointer-events: nonepra "desabilitar" botão: ainda dispara via teclado e leitores de tela ainda anunciam. - Achar que bloqueia eventos: só muda o alvo — listeners em ancestrais ainda rodam.
- Usar
pointer-events: nonepra bloquear seleção: useuser-select.

Principais takeaways
pointer-eventsé uma propriedade de hit-testing, não de desabilitar eventos.autoenonecobrem 95% dos casos reais (modais, submenus invisíveis, overlays decorativos).- Os nove valores SVG dão controle pixel-a-pixel sobre quais regiões pintadas respondem ao ponteiro.
- Herança é a fonte #1 de bugs — sempre lembre de restaurar
autonos filhos. - Pra desabilitar de verdade, use
disabled(form controls) ouinert(subárvores inteiras).
Próximos passos
- Estuda o atributo
inertpra remover interatividade com segurança de acessibilidade. - Testa os valores SVG com um demo de anel interativo pra criar intuição.
- Audita teu código procurando
pointer-events: noneem pais e confirma se cada filho interativo temautorestaurado. - Combina com
user-selectevisibilitypra construir estados de UI previsíveis.