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.

Developer inspecting CSS pointer-events property in browser DevTools while debugging click targets System Abstract Visual

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.

Frontend developer testing pointer-events none on a modal overlay to allow background button interaction Development Concept Image

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

ObjetivoFerramenta correta
Deixar cliques passarem por overlay de modalpointer-events: none no backdrop + auto no modal
Esconder submenu que não deve ser clicávelopacity: 0 + pointer-events: none
Desabilitar um botãoatributo disabled
Tornar uma seção inteira não-interativaatributo inert
Impedir seleção de textouser-select: none
Controlar qual região SVG é clicávelvalores SVG de pointer-events

Pegadinhas comuns

  • Esquecer a herança: none no pai mata a interatividade de todos os filhos silenciosamente.
  • Usar pointer-events: none pra "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: none pra bloquear seleção: use user-select.

Laptop screen showing SVG ring demo with pointer-events values being tested in a code editor Programming Illustration

Principais takeaways

  1. pointer-events é uma propriedade de hit-testing, não de desabilitar eventos.
  2. auto e none cobrem 95% dos casos reais (modais, submenus invisíveis, overlays decorativos).
  3. Os nove valores SVG dão controle pixel-a-pixel sobre quais regiões pintadas respondem ao ponteiro.
  4. Herança é a fonte #1 de bugs — sempre lembre de restaurar auto nos filhos.
  5. Pra desabilitar de verdade, use disabled (form controls) ou inert (subárvores inteiras).

Próximos passos

  • Estuda o atributo inert pra 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: none em pais e confirma se cada filho interativo tem auto restaurado.
  • Combina com user-select e visibility pra construir estados de UI previsíveis.

Fonte: CSS-Tricks Almanac — pointer-events

Este conteúdo foi elaborado com o auxílio de ferramentas de IA, com base em fontes confiáveis, e revisado pela nossa equipe editorial antes da publicação. Não substitui o aconselhamento de um profissional especializado.