motion-solid

Gestures

motion components recognise hover, tap, focus, pan and drag gestures. A while… prop animates the element while its gesture is active, and event props report the gesture's progress.

Animating during a gesture

PropActive while
whileHoverA mouse or pen pointer is over the element.
whileTapThe element is pressed.
whileFocusThe element has keyboard focus (:focus-visible).
whileDragThe element is dragged. See Drag.
whileInViewThe element is in the viewport. See Scroll animations.

Each takes values or variant names, like animate. When the gesture ends, the element animates back to the values of animate (or initial, or its style):

<motion.button whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} />

If several gestures are active, whileTap wins over whileHover, and whileDrag over both.

Hover

The hover gesture starts when a pointer enters the element and ends when it leaves. Unlike CSS :hover, it ignores touch input, so tapping on a phone doesn't leave the element stuck in its hover state. It doesn't start while something is being dragged.

PropSignature
onHoverStart(event: PointerEvent, info: EventInfo) => void
onHoverEnd(event: PointerEvent, info: EventInfo) => void

info.point holds the pointer's x and y, relative to the page.

Tap

The tap gesture starts when the primary pointer presses the element. It succeeds if the pointer is released on the element (or one of its children), and is cancelled if it's released elsewhere.

PropCalled when
onTapStartThe element is pressed.
onTapThe press ends on the element.
onTapCancelThe press ends outside the element.

Each is called with the event and an info object with the point of the event.

Press me

Last event: Waiting

A disabled <button> doesn't start tap gestures.

Keyboard

Taps work with the keyboard. An element with whileTap, onTap or onTapStart becomes focusable: if it isn't a button, link or form control and has no tabindex, it gets tabindex="0". While it has focus, pressing Enter starts a tap, releasing it ends the tap, and moving the focus away cancels it.

So onTap on a motion.div can be used from the keyboard. For actions, a motion.button is still the better choice: it has the right role for assistive technology.

Stopping propagation

A tap on a child also starts the tap gestures of its motion parents. To stop that, set propagate={{ tap: false }} on the child. Its parent's onTapStart, onTap and whileTap then don't react to presses that start on the child:

<motion.div whileTap={{ scale: 0.98 }} onTap={openCard}>
    <motion.button propagate={{ tap: false }} onTap={removeCard}>
        Remove
    </motion.button>
</motion.div>

Focus

whileFocus is active while the element has focus and matches :focus-visible. In practice, that's focus from the keyboard: clicking a button doesn't trigger it, tabbing to it does.

Move focus with the Tab key.

Pan

A pan is a pointer pressing the element and moving at least 3 pixels. Unlike drag, it doesn't move the element: use it to react to the movement in your own way.

PropCalled when
onPanSessionStartThe pointer presses the element, before it moves.
onPanStartThe pointer has moved far enough to start the pan.
onPanThe pointer moves during the pan.
onPanEndThe pointer is released.

They're called with the pointer event and a PanInfo object:

PropertyDescription
pointThe pointer's position, relative to the page.
deltaThe distance moved since the previous event.
offsetThe distance from where the pan started.
velocityThe pointer's velocity, in pixels per second.

Each is an object with x and y. onPanSessionStart only gets point.

offset: 0, 0

Gestures and variants

Gesture props accept variant names. Like animate, they flow down to the motion children: when the parent is hovered, every child with a variant of that name animates to it too. Children don't need gesture props of their own.

Solid notes

Gesture props are reactive like every other prop: they can read signals. Event handlers are read when the gesture fires, so the latest handler is always the one called.