CSS pointer-events Explained: Click-Through and Its Traps
CSS pointer-events: none makes an element ignore mouse, touch, and pen input so clicks pass through. How it works, good uses, and why it doesn't disable.
The CSS pointer-events property controls whether an element can be the target of pointer input — mouse clicks, hovers, touches, and pen taps. Set pointer-events: none and the element becomes invisible to the pointer: clicks, hovers, and taps pass straight through to whatever is underneath it. It’s the standard way to build click-through overlays and decorative layers, and it’s also one of the most commonly misused properties in CSS, because it looks like a “disable” switch and isn’t one.
The values you’ll actually use
For HTML elements, two values matter:
auto— the default. The element behaves normally.none— the element is never the target of pointer events. Hit testing skips it as if it weren’t there.
SVG supports a longer list — visiblePainted, fill, stroke, all, and others — that control whether the fill, the stroke, or the whole bounding area responds. Those let you make only the painted part of an icon clickable rather than its whole box. For ordinary HTML layout you’ll only use auto and none.
How it works under the hood
When you click, the browser performs hit testing: it finds the topmost element at those coordinates, taking stacking contexts and z-index into account. That element becomes the event target, and the event bubbles up from there.
pointer-events: none removes an element from hit testing. The browser keeps looking down the stack and targets the next element it finds. If the element is a child sitting inside its parent, that next element is usually the parent itself. If it’s an absolutely positioned overlay, it’s whatever lies beneath the overlay.
Two details trip people up:
- It’s inherited, but children can opt back in. Setting
pointer-events: noneon a container affects its descendants by inheritance, but any child withpointer-events: autobecomes clickable again — even though its parent isn’t. - Hit testing APIs respect it.
document.elementFromPoint()skips elements withpointer-events: none, which matters if you rely on that function for custom drag-and-drop logic.
Good uses for pointer-events: none
Decorative overlays
Gradient fades, vignettes, grain textures, and ::before/::after flourishes often sit on top of interactive content. Without pointer-events: none, they swallow every click meant for the buttons beneath.
.card::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(transparent, rgb(0 0 0 / 0.4));
pointer-events: none;
}
Click-through containers with clickable children
A full-screen layer that hosts toasts or a floating toolbar shouldn’t block the page. Make the layer transparent to the pointer and let its children opt back in:
.toast-layer { position: fixed; inset: 0; pointer-events: none; }
.toast-layer > .toast { pointer-events: auto; }
Cleaner event targets
Icons inside buttons can make event.target point at an <svg> or <path> rather than the button. Setting pointer-events: none on the icon keeps the target as the button, which simplifies event delegation code that inspects event.target. Using event.target.closest('button') is the more robust fix, but the CSS approach is a common belt-and-braces addition.
During drag interactions
While dragging an element, temporarily disabling pointer events on it lets the browser detect which drop zone is beneath the cursor instead of hitting the dragged element itself.
The big trap: it doesn’t disable anything
It’s tempting to “disable” a link or button by adding pointer-events: none. That only blocks the pointer. The element is still in the tab order, still focusable, and still activatable:
- A keyboard user can Tab to it and press Enter or Space.
- Screen readers still announce it as an interactive control.
- Assistive technologies can still trigger it.
So a “disabled” checkout button styled this way remains fully usable to anyone not holding a mouse — a correctness bug and an accessibility failure in one. Use the right tool instead:
| Goal | Use | Blocks pointer | Blocks keyboard | Exposed as disabled |
|---|---|---|---|---|
| Visual click-through | pointer-events: none | Yes | No | No |
| Disable a form control | disabled attribute | Yes | Yes | Yes |
| Disable a whole region | inert attribute | Yes | Yes | Hidden from AT |
| Signal unavailable but focusable | aria-disabled="true" plus JS guard | No (needs JS) | No (needs JS) | Yes |
For links, which have no disabled attribute, either remove the href (an <a> without href isn’t focusable or activatable) or render a non-link element.
Other side effects to know
Cursors and hover styles vanish. Because the element never receives hover, cursor: not-allowed on an element with pointer-events: none never shows, and :hover styles never apply. If you want a not-allowed cursor on a disabled button, put the cursor style on a wrapper or rely on the native disabled state.
Scrolling passes through too. Wheel and touch gestures over the element target whatever is underneath, so a scrollable panel with pointer-events: none can’t be scrolled by pointer.
Text selection is affected. Users can’t start a selection inside the element with the mouse.
It’s different from touch-action. touch-action controls which browser gestures — panning, pinch-zoom — are allowed on an element that still receives events. pointer-events decides whether the element receives events at all.
The takeaway
pointer-events: none removes an element from hit testing so clicks, taps, and hovers reach whatever sits beneath it. Use it for decorative overlays, click-through layers whose children opt back in with auto, and cleaner event targets. Never use it to disable controls: keyboard and assistive-technology users can still activate the element. Reach for the disabled attribute, inert, or a removed href when you mean “this can’t be used right now.”
Tagged
Keep reading
Takina · · 4 min read CSS @starting-style Explained: Entry Transitions in CSS
CSS @starting-style defines an element's before-state on first render, so transitions can animate elements as they appear, even from display: none.
Takina · · 4 min read Container Query Units: cqw, cqh, and cqi Explained
Container query units like cqw and cqi size elements relative to their containing element, not the viewport — here's how and when to use them.
Takina · · 4 min read CSS Relative Color Syntax: Deriving Colors from Colors
Relative color syntax lets CSS derive a new color from an existing one by adjusting its channels directly, no preprocessor or JS required.