Click-through layers · disabled states · hover fixes · gotchas
Three stacked layers. Toggle each layer's pointer-events on/off and click the canvas to see which layer catches the click. Watch the event log update in real time.
pointer-events: none makes an element transparent to mouse, touch, and pen input — the browser skips it entirely during hit-testing and passes the event to whatever is behind it in the stacking order.
The element still renders, still occupies layout space, and is still reachable by keyboard Tab navigation.
Production use cases. Interact with each demo — hover, click, Tab through.
Note: cursor: not-allowed only changes the cursor appearance. pointer-events: none actually blocks the click. Use both together for the best disabled state.
Without pointer-events: none, the tooltip appears over the trigger, steals the hover state, and the tooltip disappears. With it, hover stays on the trigger element permanently.
The gradient overlay renders above the buttons but clicks pass through it to the interactive elements below. No JavaScript event forwarding needed.
Click the purple button — it works even though the red-dashed parent has pointer-events: none. The child's auto override re-enables just that element.
SVG-specific values give precise control over which part of an SVG element is "clickable" — just the fill, just the stroke, or the entire bounding box.
Click Submit to see the form lock during "loading". pointer-events: none on the form prevents double-submission without disabling each input individually.
Four ways pointer-events surprises developers — and how to handle each.
If you hide an element with opacity: 0 without adding pointer-events: none, the element is invisible but still clickable — a "ghost button".
Try clicking the LEFT half — that's the visible button. Then the RIGHT half — surprise: an invisible ghost button is sitting there too!
pointer-events: none blocks mouse, touch, and pen — but keyboard navigation via Tab still reaches the element. Screen readers can also activate it.
Tab to the red button and press Enter — it fires despite pointer-events: none. For a true disabled state, also set tabindex="-1" and aria-disabled="true".
They look similar but do completely different things. Hover each box:
If a child element has pointer-events: auto inside a parent with none, the child fires events that bubble UP through the parent, triggering any parent event listeners.
The parent itself cannot be clicked directly. But if the child fires a click, it bubbles through the parent's DOM node and triggers any parent listeners.
pointer-events: none blocks click, hover, and tap. But on mobile, scroll and pinch-zoom are touch gestures, not pointer events. To also block scroll/zoom on an element, add touch-action: none.
Be careful — blocking touch-action on large areas can make the page feel broken on mobile.