Svelte Motion brings a Framer Motion-style API to Svelte 5 with motion.<tag> components, gestures, variants, exit animations, layout animation, and utility hooks.
For the latest documentation and examples, visit motion.svelte.page.
npm install @humanspeak/svelte-motion
<script lang="ts">
import { motion } from '@humanspeak/svelte-motion'
</script>
<motion.button initial={{ opacity: 0 }} animate={{ opacity: 1 }} whileTap={{ scale: 0.97 }}>
Hello motion
</motion.button>
Goal: Framer Motion API parity for Svelte where common React examples can be translated with minimal changes.
| Capability | Status |
|---|---|
initial / animate / transition |
Supported |
variants (string keys + inheritance) |
Supported |
whileHover / whileTap / whileFocus / whileInView |
Supported |
Drag (drag, constraints, momentum, controls, callbacks) |
Supported |
AnimatePresence (initial, mode, onExitComplete) |
Supported |
Layout (layout, layout="position") |
Supported (single-element FLIP) |
Shared layout (layoutId) |
Not yet supported |
Pan gesture API (whilePan, onPan*) |
Not yet supported |
MotionConfig parity beyond transition |
Partial |
reducedMotion, features, transformPagePoint |
Not yet supported |
Motion components are generated from canonical HTML/SVG tag lists and exported from src/lib/html/.
motion.div, motion.button, motion.svg, motion.path, etc.script, style, link, meta, title, head, html, body.motion.<tag>Use motion components the same way you use regular elements, with animation props:
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
/>
MotionConfigMotionConfig currently supports default transition values for descendants.
<script lang="ts">
import { MotionConfig, motion } from '@humanspeak/svelte-motion'
</script>
<MotionConfig transition={{ duration: 0.4 }}>
<motion.div animate={{ scale: 1.05 }} />
</MotionConfig>
AnimatePresenceExit animations on unmount with support for mode="sync" | "wait" | "popLayout" and onExitComplete.
<script lang="ts">
import { AnimatePresence, motion } from '@humanspeak/svelte-motion'
let show = $state(true)
</script>
<AnimatePresence mode="wait" onExitComplete={() => console.log('done')}>
{#if show}
<motion.div
key="card"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.2 } }}
/>
{/if}
</AnimatePresence>
<motion.button whileTap={{ scale: 0.97 }} onclick={() => (show = !show)}>Toggle</motion.button>
Notes:
AnimatePresence require key.{ duration: 0.35 } < merged transition < exit.transition.whileHover<motion.button whileHover={{ scale: 1.05, transition: { duration: 0.12 } }} />
(hover: hover) and (pointer: fine)).onHoverStart and onHoverEnd.whileTap<motion.button whileTap={{ scale: 0.95 }} />
onTapStart, onTap, onTapCancel.whileFocus<motion.button whileFocus={{ scale: 1.05, outline: '2px solid blue' }} />
onFocusStart and onFocusEnd.whileInView<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
onInViewStart={() => console.log('entered')}
onInViewEnd={() => console.log('left')}
/>
IntersectionObserver.viewport options yet).Supported drag props:
drag: true | 'x' | 'y'dragConstraints: pixel object or element refdragElastic, dragMomentum, dragTransitiondragDirectionLock, dragPropagation, dragSnapToOrigindragListener, dragControlswhileDragonDragStart, onDrag, onDragEnd, onDirectionLock, onDragTransitionEnd<script lang="ts">
import { createDragControls, motion } from '@humanspeak/svelte-motion'
const controls = createDragControls()
</script>
<button onpointerdown={(e) => controls.start(e)}>Start drag</button>
<motion.div
drag="x"
dragControls={controls}
dragListener={false}
dragConstraints={{ left: -120, right: 120 }}
whileDrag={{ scale: 1.03 }}
/>
<script lang="ts">
import { motion, type Variants } from '@humanspeak/svelte-motion'
let open = $state(false)
const parent: Variants = {
open: { opacity: 1 },
closed: { opacity: 0 }
}
const child: Variants = {
open: { x: 0, opacity: 1 },
closed: { x: -16, opacity: 0 }
}
</script>
<motion.ul variants={parent} initial="closed" animate={open ? 'open' : 'closed'}>
<motion.li variants={child}>Item A</motion.li>
<motion.li variants={child}>Item B</motion.li>
</motion.ul>
variants.Single-element FLIP layout animation:
<motion.div layout />
<motion.div layout="position" />
layout: translate + scale.layout="position": translate only.layoutId) is not implemented yet.useAnimationFrameuseSpringuseTimeuseTransformstyleStringstringifyStyleObject (deprecated)createDragControlsThe package also re-exports core helpers from motion (for example animate, stagger, transform, easings, and utility functions).
initial (or first animate keyframe when initial is empty).initial={false} skips initial enter animation.Validated against current source and test suite (local run):
259 passed78 passed, 1 skippedlayoutId, LayoutGroup).whilePan, onPan*).whileInView does not yet expose Framer-style viewport options.MotionConfig currently only provides transition defaults.reducedMotion, features, and transformPagePoint are not implemented.motionmotion-domMIT © Humanspeak, Inc.
Made with ❤️ by Humanspeak