Why pointer-events Is Misunderstood
Most developers first meet pointer-events: none when a modal overlay is eating their clicks. They slap it on, the clicks pass through, and they move on — without ever understanding what the property actually does.
Here's the mental model that fixes everything: pointer-events does not disable an element. It changes which element becomes the event target during hit-testing.
When the browser receives a pointer event, it runs a hit-test: it walks down from the topmost element under the cursor until it finds one that accepts pointer events. If the topmost element has pointer-events: none, the browser skips it and keeps looking underneath. That's it. That's the whole property.
Once you internalize this, the SVG values, the inheritance quirks, and the modal pattern all fall into place. For a deeper look at how modern tooling is evolving around this kind of low-level behavior, see our analysis of agentic retrieval systems like Foundry IQ.

Syntax and Core Values
pointer-events: auto | bounding-box | visiblePainted | visibleFill | visibleStroke
| visible | painted | fill | stroke | all | none;
- Initial:
auto - Inherited: yes
- Applies to: all elements (SVG container, graphics, and
<use>elements) - Animation type: discrete
The two values you'll actually use
/* Default: element behaves normally and receives pointer events */
.interactive {
pointer-events: auto;
}
/* Element is skipped during hit-testing; events fall through to what's below */
.overlay {
pointer-events: none;
}
The nine SVG-only values
/* Only fire when visible AND pointer is over a painted fill/stroke */
.ring-visible-painted { pointer-events: visiblePainted; }
/* Fire when visible and over the fill, even if fill: none */
.ring-visible-fill { pointer-events: visibleFill; }
/* Fire when visible and over the stroke, even if stroke: none */
.ring-visible-stroke { pointer-events: visibleStroke; }
/* Visible + over fill OR stroke, regardless of fill/stroke values */
.ring-visible { pointer-events: visible; }
/* Painted area only — ignores visibility entirely */
.ring-painted { pointer-events: painted; }
/* Fill area only — ignores fill and visibility values */
.ring-fill { pointer-events: fill; }
/* Stroke area only — ignores stroke and visibility values */
.ring-stroke { pointer-events: stroke; }
/* Whole bounding box, even unpainted regions */
.ring-bbox { pointer-events: bounding-box; }
/* Fill OR stroke, regardless of fill/stroke/visibility */
.ring-all { pointer-events: all; }
The Inheritance Trap (and How to Escape It)
pointer-events is inherited. Set it to none on a parent and every descendant inherits none. This is exactly why the modal pattern needs two rules, not one:
/* Full-viewport container that centers the modal */
.modal-backdrop {
pointer-events: none; /* let clicks reach the page underneath */
}
/* The modal itself must opt back in */
.modal-backdrop > .modal {
pointer-events: auto;
}
Forget the second rule and your modal becomes completely unclickable — a classic bug that costs teams hours of debugging.
The same pattern applies to invisible submenus. If you hide a dropdown with opacity: 0, it's still in the layout and still catches pointer events. Adding pointer-events: none fixes the invisible-but-clickable problem. For teams building complex UI state machines, this is the same class of determinism problem we covered in our deep dive on floating-point determinism in CUDA reductions — small, invisible state changes that break everything downstream.

What pointer-events Does NOT Do
Three common misconceptions to unlearn:
1. It does not stop event propagation
pointer-events only affects target selection. Once an element is chosen as event.target, the event follows normal capture and bubble phases. A parent with pointer-events: none will still receive click, pointerenter, and pointerleave events if its child (with pointer-events: auto) is the actual target.
// This listener FIRES even though the parent has pointer-events: none
parent.addEventListener('click', (e) => {
console.log('Bubbled from:', e.target); // the child
});
2. It does not disable an element
The element remains keyboard-focusable via Tab, and keyboard interactions still work. If you actually want to disable a form control, use the disabled attribute. If you want to remove an entire subtree from pointer input, keyboard focus, and the accessibility tree, use the inert attribute.
<!-- Wrong: form control still submits via keyboard -->
<input type="submit" style="pointer-events: none">
<!-- Right: actually disabled -->
<input type="submit" disabled>
3. It does not prevent text selection
Users can still select text inside a pointer-events: none element with Ctrl/Cmd + A. Text selection is governed by the user-select property, not by hit-testing.
.no-select {
user-select: none;
}
Practical Checklist
| Goal | Correct tool |
|---|---|
| Let clicks pass through a modal overlay | pointer-events: none on backdrop + auto on modal |
| Hide a submenu that shouldn't be clickable | opacity: 0 + pointer-events: none |
| Disable a button | disabled attribute |
| Make a whole section non-interactive for all input | inert attribute |
| Stop text selection | user-select: none |
| Control which SVG sub-region is clickable | SVG-only pointer-events values |
Common Pitfalls
- Forgetting inheritance: setting
noneon a parent silently kills interactivity for every child. - Using
pointer-events: noneto "disable" a button: it still fires via keyboard and screen readers may still announce it. - Assuming it blocks events: it only changes the target — listeners on ancestors still run.
- Using
pointer-events: nonefor text selection blocking: useuser-selectinstead.

Key Takeaways
pointer-eventsis a hit-testing property, not an event-disabling property.autoandnonecover 95% of real-world use cases (modals, invisible submenus, decorative overlays).- The nine SVG-only values give you pixel-level control over which painted regions respond to the pointer.
- Inheritance is the #1 source of bugs — always remember to opt children back in with
auto. - For true disabling, use
disabled(form controls) orinert(whole subtrees).
Next Steps
- Explore the
inertattribute for accessibility-safe interactivity removal. - Study the SVG
pointer-eventsvalues with a live ring demo to build intuition. - Audit your codebase for
pointer-events: noneon parents and verify every interactive child hasautorestored. - Combine with
user-selectandvisibilityto build fully predictable UI states.