Docs/Guides/Gestures

Gestures

Every motion.* element accepts gesture variants. They take precedence over animate while the gesture is active and return to the base animation when it ends.

<motion.button
  whileHover={{ scale: 1.05 }}
  whileTap={{ scale: 0.95 }}
  whileFocus={{ boxShadow: '0 0 0 4px var(--ring)' }}
/>
compound
hover-tap-stack.tsxOpen ↗
import { motion } from 'motion-solidjs'
import { For } from 'solid-js'

export const meta = {
  slug: 'hover-tap-stack',
  title: 'Hover + tap stack',
  category: 'gestures',
  description: 'A row of chips that lift on hover and depress on tap, with a per-item spring.',
  tag: 'compound',
} as const

const labels = ['Apple', 'Lime', 'Berry', 'Mint']
const grads = ['bg-grad-rose', 'bg-grad-amber', 'bg-grad-violet', 'bg-grad-mint']

export default function HoverTapStack() {
  return (
    <div class="flex gap-2">
      <For each={labels}>
        {(label, i) => (
          <motion.button
            class={`rounded-2xl ${grads[i()]} px-4 py-2 text-xs font-semibold text-white`}
            whileHover={{ y: -6, scale: 1.05 }}
            whileTap={{ scale: 0.9 }}
            transition={{ type: 'spring', stiffness: 350, damping: 16 }}
          >
            {label}
          </motion.button>
        )}
      </For>
    </div>
  )
}

whileHover, whileTap, whileFocus

These props accept the same target shapes as animate: style objects, variant labels, or variant functions.

whileHover
hover.tsxOpen ↗
import { motion } from 'motion-solidjs'

export const meta = {
  slug: 'hover',
  title: 'Hover',
  category: 'gestures',
  description: 'whileHover springs the box up and changes its background.',
  tag: 'whileHover',
} as const

export default function Hover() {
  return (
    <motion.div
      class="h-20 w-20 rounded-2xl bg-grad-violet shadow-glow"
      whileHover={{ y: -10, scale: 1.08, rotate: -4 }}
      transition={{ type: 'spring', stiffness: 320, damping: 14 }}
    />
  )
}

whileInView

Animates the element while it is in the viewport. Add a viewport prop to control thresholds, margins, and one-time playback.

whileInView
Card 1
Card 2
Card 3
Card 4
Card 5
Card 6
Card 7
Card 8
while-in-view.tsxOpen ↗
import { motion } from 'motion-solidjs'
import { For, createSignal } from 'solid-js'

export const meta = {
  slug: 'while-in-view',
  title: 'While in view',
  category: 'scroll',
  description: 'Each card animates as it enters the viewport.',
  tag: 'whileInView',
} as const

export default function WhileInView() {
  const [container, setContainer] = createSignal<HTMLDivElement | null>(null)
  return (
    <div
      ref={setContainer}
      class="h-64 w-56 overflow-y-scroll rounded-2xl border border-border bg-card p-4"
    >
      <For each={[0, 1, 2, 3, 4, 5, 6, 7]}>
        {(i) => (
          <motion.div
            class="mb-3 grid h-16 place-items-center rounded-2xl bg-grad-amber text-xs font-semibold text-white"
            initial={{ opacity: 0, x: -40 }}
            whileInView={{ opacity: 1, x: 0 }}
            viewport={{ root: container() ?? undefined, amount: 0.5 }}
            transition={{ type: 'spring', stiffness: 240, damping: 22 }}
          >
            Card {i + 1}
          </motion.div>
        )}
      </For>
    </div>
  )
}

Pan

For pointer interactions that should not become a draggable element, use onPan, onPanStart, and onPanEnd.

onPan
pan.tsxOpen ↗
import { motion } from 'motion-solidjs'
import { createSignal } from 'solid-js'

export const meta = {
  slug: 'pan',
  title: 'Pan gesture',
  category: 'gestures',
  description: 'Track pointer offset across pan events.',
  tag: 'onPan',
} as const

export default function Pan() {
  const [offset, setOffset] = createSignal({ x: 0, y: 0 })

  return (
    <motion.div
      class="h-24 w-24 cursor-grab rounded-2xl bg-grad-mint shadow-glow active:cursor-grabbing"
      onPan={(_, info) => setOffset({ x: info.offset.x, y: info.offset.y })}
      onPanEnd={() => setOffset({ x: 0, y: 0 })}
      style={{
        transform: `translate(${offset().x * 0.3}px, ${offset().y * 0.3}px)`,
      }}
    />
  )
}