Documentation and patterns for using motion-sv, a Svelte 5 port of Motion (Framer Motion). Use this when the user wants animations, gestures, or transitions in Svelte.
A port of the Motion library (formerly Framer Motion) specifically for Svelte 5. It aligns closely with the motion-v API structure rather than the React version.
motion-sv.motionThe primary component factory. Use dot notation to render any HTML element.
<script>
import { motion } from "motion-sv";
</script>
<!-- Sections & Headings -->
<motion.section initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
<motion.h1 animate={{ y: 0 }}>Headline</motion.h1>
</motion.section>
<!-- Links & Buttons -->
<motion.a
href="/about"
whileHover={{ scale: 1.05 }}
whilePress={{ scale: 0.95 }}
>
Go to About
</motion.a>
ALWAYS pass styles as an object via style={{ key: value }}, never as a string. This is required to bind MotionValues correctly without triggering re-renders.
<script>
import { motion, useMotionValue } from "motion-sv";
const x = useMotionValue(0);
</script>
<!-- ❌ BAD: String syntax (Values won't update) -->
<motion.div style="background-color: red; transform: translateX(10px)" />
<!-- ✅ GOOD: Object syntax -->
<motion.div
style={{
x,
backgroundColor: "#ff0000",
"--custom-var": 100,
}}
/>
For better developer experience and type safety, define variants using the Variants type.
<script lang="ts">
import { motion, type Variants } from "motion-sv";
const boxVariants: Variants = {
hidden: { opacity: 0, scale: 0.8 },
visible: {
opacity: 1,
scale: 1,
transition: { duration: 0.5 },
},
};
</script>
<motion.div variants={boxVariants} initial="hidden" animate="visible" />
initial, animate, exit, transition, variants.whileHover, whilePress (preferred over whileTap), whileDrag, whileFocus.drag (boolean | "x" | "y"), dragConstraints, dragElastic, dragMomentum.onAnimationStart, onAnimationComplete, onUpdate.onHoverStart, onHoverEnd, onPress, onPressStart, onPressEnd.Important: Use inViewOptions instead of the React viewport prop.
<motion.section
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
inViewOptions={{
once: true,
amount: "some", // "some" | "all" | 0..1
margin: "0px 0px -200px 0px",
}}
>
Hello
</motion.section>
AnimatePresenceEnables exit animations.
Modes: "sync" (default), "wait", "popLayout".
<script>
import { motion, AnimatePresence } from "motion-sv";
let show = $state(true);
</script>
<AnimatePresence mode="wait">
{#if show}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
key="unique-key"
/>
{/if}
</AnimatePresence>
Svelte lacks getSnapshotBeforeUpdate. You MUST use createLayoutMotion for layout animations.
Pattern:
const layout = createLayoutMotion(motion).<layout.div> instead of <motion.div>.layout.update.with(fn).<script>
import { motion, createLayoutMotion } from "motion-sv";
let isOn = $state(false);
const layout = createLayoutMotion(motion);
// Wrap state mutation
const toggle = layout.update.with(() => (isOn = !isOn));
</script>
<div onclick={toggle}>
<!-- Use layout.div and layoutDependency or layoutId -->
<layout.div
layoutDependency={isOn}
transition={{ type: "spring", stiffness: 700, damping: 30 }}
/>
</div>
Use specific components for reordering lists.
<script>
import { ReorderGroup, ReorderItem } from "motion-sv";
let items = $state([0, 1, 2]);
</script>
<ReorderGroup axis="y" bind:values={items}>
{#each items as item (item)}
<ReorderItem value={item}>
{item}
</ReorderItem>
{/each}
</ReorderGroup>
Reduce bundle size by loading features on demand.
<script>
import { LazyMotion, domAnimation } from "motion-sv";
</script>
<LazyMotion features={domAnimation}>
<!-- Children using motion components -->
</LazyMotion>
motion-v prop naming (e.g., inViewOptions, whilePress).transition:fn.style={{ ... }}.