Hover Trigger

Play animations on mouse enter with reverse or restart on leave.

.onHover() plays a timeline when the pointer enters a target element and reacts when the pointer leaves — reversing, restarting, or doing nothing based on your configuration.

Basic usage

Chain .onHover() after defining your animation. By default, leaving the element does nothing. Pass onLeave: 'reverse' to smoothly reverse back to the start state.


Options

OptionTypeDefaultDescription
eachbooleanfalseCreate an independent timeline instance per matched element.
onLeave'reverse' | 'restart' | 'none''none'What to do when the pointer leaves the element.

onLeave behavior

Control what happens when the pointer leaves the element:

  • 'reverse' (recommended) — smoothly plays the timeline in reverse back to the start state.
  • 'restart' — snaps immediately to the start state and replays the animation forward.
  • 'none' (default) — does nothing; the animation stays at its end state after hover.
typescript
// Smoothly reverses on leave — feels natural for most hover effects
Motion("btn", ".btn", {
  to: { scale: 1.05, backgroundColor: "#7744FF" },
  duration: 0.25,
  ease: "power2.out",
}).onHover({ onLeave: "reverse" });

// Snaps back and replays — good for looping-style feedback
Motion("badge", ".badge", {
  to: { rotate: 10 },
  duration: 0.2,
}).onHover({ onLeave: "restart" });

// Stays at end state — useful when hover reveals persistent UI
Motion("tooltip", ".tooltip", {
  from: { opacity: 0, y: 4 },
  duration: 0.2,
}).onHover({ onLeave: "none" });

each — independent per-element instances

When a selector matches multiple elements, all elements share one timeline by default — hovering any one of them plays the animation for all. Use each: true to give every element its own independent timeline instance.

typescript
// Without each — hovering any card affects all cards simultaneously
Motion("cards", ".card", {
  to: { y: -8 },
  duration: 0.3,
}).onHover({ onLeave: "reverse" });

// With each — each card responds independently
Motion("cards", ".card", {
  to: { y: -8 },
  duration: 0.3,
}).onHover({ each: true, onLeave: "reverse" });

each: true is almost always what you want when the selector targets a list of interactive items like cards, buttons, nav links, or gallery items.


Card lift with shadow

typescript
Motion("card-hover", ".card", {
  to: { y: -8, boxShadow: "0 12px 24px rgba(0,0,0,0.15)" },
  duration: 0.3,
  ease: "power2.out",
}).onHover({ each: true, onLeave: "reverse" });

Scale on hover

typescript
Motion("scale-hover", ".thumb", {
  to: { scale: 1.06 },
  duration: 0.25,
  ease: "power2.out",
}).onHover({ each: true, onLeave: "reverse" });

Background color change

typescript
Motion("color-hover", ".nav-link", {
  to: { backgroundColor: "#f0ebff", color: "#6633EE" },
  duration: 0.2,
  ease: "power1.out",
}).onHover({ each: true, onLeave: "reverse" });

Reveal an overlay on hover

typescript
Motion("overlay-hover", ".gallery-item", {
  to: { opacity: 1 },
  duration: 0.3,
  ease: "power2.out",
}).onHover({ each: true, onLeave: "reverse" });

Staggered grid ripple on hover

typescript
Motion("grid-hover", ".cell", {
  to: { scale: 1.1, backgroundColor: "#6633EE" },
  duration: 0.3,
  stagger: {
    each: 0.03,
    from: "center",
    grid: "auto",
  },
  ease: "power2.out",
}).onHover({ onLeave: "reverse" });

Using to for hover effects

Hover effects typically use to rather than from — the element starts at its natural CSS state and animates to the hover state. The SDK reads the current computed styles as the from automatically.

typescript
// ✅ to-only — starts at natural state, animates to hover values
Motion("btn-hover", ".btn", {
  to: { scale: 1.08, letterSpacing: "0.05em" },
  duration: 0.25,
  ease: "power2.out",
}).onHover({ each: true, onLeave: "reverse" });

// ❌ Unnecessary — natural defaults (scale: 1) don't need to be declared in from
Motion("btn-hover", ".btn", {
  from: { scale: 1 },
  to: { scale: 1.08 },
  duration: 0.25,
}).onHover({ each: true, onLeave: "reverse" });