Motion for Svelte (motion-sv)
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.
Stack Requirements
- MUST be used with Svelte 5 (Runes).
- Package name:
motion-sv.
Core Components
motion
The 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>
Styling Pattern (Important)
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,
}}
/>
Type Safety & Variants
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" />
Supported Props
- Animation:
initial,animate,exit,transition,variants. - Gestures:
whileHover,whilePress(preferred overwhileTap),whileDrag,whileFocus. - Drag:
drag(boolean | "x" | "y"),dragConstraints,dragElastic,dragMomentum. - Events:
onAnimationStart,onAnimationComplete,onUpdate. - Gesture Events:
onHoverStart,onHoverEnd,onPress,onPressStart,onPressEnd.
Viewport / Scroll Animations (Vue API Style)
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>
AnimatePresence
Enables 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>
Layout Animations (CRITICAL DIFFERENCE)
Svelte lacks getSnapshotBeforeUpdate. You MUST use createLayoutMotion for layout animations.
Pattern:
- Create a proxy object:
const layout = createLayoutMotion(motion). - Use
<layout.div>instead of<motion.div>. - Wrap state updates with
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>
Reorder (Drag & Drop Lists)
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>
Performance (Lazy Motion)
Reduce bundle size by loading features on demand.
<script>
import { LazyMotion, domAnimation } from "motion-sv";
</script>
<LazyMotion features={domAnimation}>
<!-- Children using motion components -->
</LazyMotion>
Best Practices
- API Alignment: Follow
motion-vprop naming (e.g.,inViewOptions,whilePress). - Directives: Do NOT use Svelte's
transition:fn. - Styles: Always use the object syntax
style={{ ... }}.