Hover keeps neighbours and its own hit box still, and previews wait for the pointer to settle

Sweep a pointer across a row of cards and every card fires its preview, or the hovered card lifts out from under the pointer and flickers. A wait armed by pointermove, and a lift on a child instead of the hit box, fix both.

Lab · Impeccable · animate · Proposal: a short Hover section in animate.md, one Verify bullet, one operate.md bullet · Issue #1261 on GitHub →

Try it

Sweep, settle, scroll

Problem todayProposed fix the issue's guidance

Only a still pointer wakes a card; entering arms nothing

ProblemPlain :hover reveal
wakes 0enters 0moves 0awake now 0
Every enter is a wake. Measured: a sweep of one row woke 3 each time (10, 30, 60 px steps); a scroll under a still pointer woke 1 with 0 moves.
Proposed fixWait armed by pointermove
wakes 0enters 0moves 0awake now 0speed – px/ms
Dashed outline = wait armed. Measured: the same sweeps woke 0; settling woke the card after about 400 ms (0 at 150 ms); a scroll under a still pointer woke 0.

(i) Sweep fast across either grid: the left wakes every card it crosses, the right none. (ii) Rest on a card: the right wakes it after the wait. (iii) Park the pointer on a card, press S (or the scroll button and move back before the countdown ends): the cards scroll under a still pointer, pointerenter fires with no pointermove, and only the left grid wakes. (iv) Turn on Warm handoff, settle on one card, then move to its neighbour within the window: it wakes at once, and so do the ones you sweep over next.

Event log
01

The problem

What a plain :hover does to a dense surface today. Each row links to its before/after pair.

02

What the proposal does

The same cases, today beside the issue's guidance: control feedback answers at once; optional previews wait for the pointer to settle, armed by the first pointermove; a speed gate restarts the wait; the hit box never moves.

Hit boxpointer 2 px inside the edge

The element under the pointer stays put; a child carries the surface and the motion

ProblemHover target lifts itself
.card:hover { transform }
Card
hovered   enters 0   leaves 0
Measured (pointer 2 px inside, 4 px lift, 40 one-pixel moves): 47–48 enters, 48 leaves; with a 150 ms transition 21–23.
Proposed fixInner surface lifts
.card:hover .surface { transform }
Card
hovered   enters 0   leaves 0
Same gesture: 0 enters, 0 leaves, with or without the transition.

(v) The dashed box is the resting hit box; the hatched band at its bottom is as tall as the lift. Rest the pointer inside that band and nudge it by a pixel or two. When the hovered card itself lifts, it slides out from under the pointer, un-hovers, drops back and re-enters. When a child lifts, the hit box never moves.

Interrupted entranceleave at 40% of the reveal

Leaving an item that woke runs its exit once, from its current state

ProblemEntrance and exit keyframes swapped
Restartsfrom fully open
Preview
Measured progress: 0.89 before leave, 1.00 the same tick (jumps open), then 0.79.
Proposed fixTransition on the state class
Reversesfrom current state
Preview
Measured progress: 0.89 before leave, 0.89 the same tick, 0.48 next frame.

(vi) Hover the cards for real (no wait here, to isolate the exit), or press the button, which wakes both and leaves at 40% of the reveal. A transition reverses from where it is. Swapping in a separate exit keyframe starts from fully open, so the preview jumps.

this runbefore leavesame ticknext frame+60 ms
03

Where it shows up

Surfaces where the same rules apply. The values are the author's own, as in the issue: examples, not proposed defaults. Sketches, not live.

Proposed fixPortfolio card grid
Projectposter
Projectvideo wakes
Projectposter
Video preview on settle. Wait 400 ms, gate 0.6 px/ms, linger 1200 ms, exit 280 ms. No handoff: a sweep plays nothing.
Proposed fixMetric card and action strip
Revenuecard body wakes the details
Card body waits 280 ms; its narrow action strip 100 ms; both gate at 0.6 px/ms.
Proposed fixToolbar tooltips
Bold
Where handoff is right: once one tooltip is awake, the next wakes without the wait (Radix Tooltip's skipDelayDuration). Wrong for a grid of video previews.
Probe notes method and the measurements behind the issue

Every pair is live: move the real pointer over it. Numbers in captions were measured in Chrome Canary 159 with trusted CDP input (below); the counters on the page are yours. Each group's bar stays in view while you look: its controls change only that group. Values are examples, not proposed defaults.

Chrome Canary 159.0.8128.2 (Windows), trusted mouse and wheel input sent over raw CDP (Input.dispatchMouseEvent), own tab. Probe-page figures come from separate minimal pages; the demo figures come from this page with the same geometry. Lift counts depend on layout, so the two differ.

CaseResult
Card scrolled under a still pointer (instant scrollTop or a wheel step)pointerover/pointerenter (and the mouse events) fire in the same task as the scroll; :hover matches one frame later; pointermove never fires. Each wheel tick wakes the card under the pointer.
Same, smooth scrollTo (14 scroll events over about 235 ms)Nothing during the animation; all events and the :hover change land about 59 ms after the last scroll event, at scrollend. Cards passed in between are never hovered.
Card lifts itself (.card:hover { transform: translateY(-4px) }), pointer 2 px inside its bottom edge, 40 one-pixel moves 25 ms apartProbe page: 41 to 48 enters across runs, about one per move. This demo: 47–48 enters, 48 leaves (3 repeats).
Same, with transition: transform 150msProbe page: 18 to 23 enters. This demo: 21–23 (3 repeats).
Same, but the card stays put and an inner element lifts0 enters, 0 leaves
Pointer at rest 2 px inside the edge, no transitionKeeps toggling: 3–4 enters in the first 300 ms, 13 by 1.8 s on this page; the probe page settled after about 3 toggles. Where it ends (hovered or not) varied between runs.
Hovered card scale(1.05) with z-index: 2, 4 px gapNeighbor's getBoundingClientRect() unchanged; elementFromPoint just inside the neighbor's edge returns the hovered card
Same scale on an inner layer with pointer-events: noneThe neighbor keeps its edge
Fast sweep of one row here (10, 30, 60 px steps, about 0.7 px/ms)Plain :hover grid wakes 3 each time; proposed grid wakes 0
Settle on a card / slow pass at about 0.2 to 0.3 px/msWakes after about 400 ms; 0 wakes at 150 ms. A slow pass wakes it once, after 400 ms
Handoff on, neighbor reached within the window / 500 ms after (window 300 ms)Woken with no wait, and handoff chains across a sweep / not woken
Exit interrupted at 40% of the revealTransition: 0.89, 0.89 same tick, 0.48 next frame (reverses). Keyframe swap: 0.89, 1.00 same tick (jumps to open), then 0.79

Lift geometry: a target that moves slides out from under the pointer, so it toggles while the pointer sits in that band. A 4 px lift was measured; a 2 px lift has a 2 px band by the same geometry (not measured). The hit-target case was measured with z-index raised only.

Example values from the author's own surfaces, not proposed defaults:

SurfaceWait before revealSpeed gateExit
Portfolio cards (Persuade, shipped)400 ms0.6 px/mslingers 1200 ms, then exits in 280 ms
Dashboard metric card body (Operate, tuning playground)280 ms0.6 px/ms
Narrow action strip on that card (tuning playground)100 ms0.6 px/ms

hoverIntent v1.10.2 defaults: interval: 100, sensitivity: 6, so it fires when the pointer moves under 6 px between 100 ms polls, roughly 0.06 px/ms.

Untested: Firefox, Safari, touchpads, touchpad inertia, a physical (smoothed) wheel, reduced-motion emulation, an unraised scale, the exit linger.

Investigation, probe and demo prepared with AI assistance (Claude Code) under the author's direction.