@humanspeak/svelte-motion
Version:
Framer Motion for Svelte 5. Declarative motion.<tag> components with AnimatePresence exit animations, gestures (hover, tap, drag, focus, in-view), variants, FLIP layout animations, shared-layout transitions, spring physics, and scroll-linked motion values
300 lines (220 loc) • 10.1 kB
Markdown
# Svelte Motion — Framer Motion API for Svelte 5
[](https://www.npmjs.com/package/@humanspeak/svelte-motion)
[](https://github.com/humanspeak/svelte-motion/actions/workflows/npm-publish.yml)
[](https://coveralls.io/github/humanspeak/svelte-motion?branch=main)
[](https://github.com/humanspeak/svelte-motion/blob/main/LICENSE)
[](https://www.npmjs.com/package/@humanspeak/svelte-motion)
[](https://github.com/humanspeak/svelte-motion/actions/workflows/codeql.yml)
[](https://packagephobia.com/result?p=@humanspeak/svelte-motion)
[](https://trunk.io)
[](http://www.typescriptlang.org/)
[](https://www.npmjs.com/package/@humanspeak/svelte-motion)
[](https://github.com/humanspeak/svelte-motion/graphs/commit-activity)
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](https://motion.svelte.page).
## Install
```bash
npm install @humanspeak/svelte-motion
```
```svelte
<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>
```
## Framer Motion API Parity
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, function-form `custom`) | Supported |
| `whileHover` / `whileTap` / `whileFocus` / `whileDrag` / `whileInView` | Supported (inline + variant keys / arrays) |
| Drag (`drag`, constraints, momentum, controls, callbacks) | Supported |
| `AnimatePresence` (`initial`, `mode`, `onExitComplete`) | Supported |
| Layout (`layout`, `layout="position"`) | Supported (single-element FLIP) |
| Shared layout (`layoutId`, `LayoutGroup`, `layoutScroll`) | Supported |
| Pan gesture API (`whilePan`, `onPan*`) | Not yet supported |
| `MotionConfig` parity beyond `transition` | Partial |
| `reducedMotion`, `features`, `transformPagePoint` | Not yet supported |
## Supported elements
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.
- Most standard tags are included.
- Excluded by generation: `script`, `style`, `link`, `meta`, `title`, `head`, `html`, `body`.
## Core components
### `motion.<tag>`
Use motion components the same way you use regular elements, with animation props:
```svelte
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
/>
```
### `MotionConfig`
`MotionConfig` currently supports default `transition` values for descendants.
```svelte
<script lang="ts">
import { MotionConfig, motion } from '@humanspeak/svelte-motion'
</script>
<MotionConfig transition={{ duration: 0.4 }}>
<motion.div animate={{ scale: 1.05 }} />
</MotionConfig>
```
### `AnimatePresence`
Exit animations on unmount with support for `mode="sync" | "wait" | "popLayout"` and `onExitComplete`.
```svelte
<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:
- Direct children of `AnimatePresence` require `key`.
- Exit transition precedence: base `{ duration: 0.35 }` < merged `transition` < `exit.transition`.
## Interaction props
### `whileHover`
```svelte
<motion.button whileHover={{ scale: 1.05, transition: { duration: 0.12 } }} />
```
- Uses true-hover gating (`(hover: hover)` and `(pointer: fine)`).
- Supports `onHoverStart` and `onHoverEnd`.
### `whileTap`
```svelte
<motion.button whileTap={{ scale: 0.95 }} />
```
- Supports `onTapStart`, `onTap`, `onTapCancel`.
- Keyboard accessible (Enter/Space).
### `whileFocus`
```svelte
<motion.button whileFocus={{ scale: 1.05, outline: '2px solid blue' }} />
```
- Supports `onFocusStart` and `onFocusEnd`.
### `whileInView`
```svelte
<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
onInViewStart={() => console.log('entered')}
onInViewEnd={() => console.log('left')}
/>
```
- Uses `IntersectionObserver`.
- Current implementation uses a fixed threshold behavior (no Framer-style `viewport` options yet).
## Drag
Supported drag props:
- `drag`: `true | 'x' | 'y'`
- `dragConstraints`: pixel object or element ref
- `dragElastic`, `dragMomentum`, `dragTransition`
- `dragDirectionLock`, `dragPropagation`, `dragSnapToOrigin`
- `dragListener`, `dragControls`
- `whileDrag`
- Callbacks: `onDragStart`, `onDrag`, `onDragEnd`, `onDirectionLock`, `onDragTransitionEnd`
```svelte
<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 }}
/>
```
## Variants
```svelte
<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>
```
- String variant keys are resolved from `variants`.
- Variant state inherits through context.
- A variant entry can be a `(custom) => keyframes` factory. The `custom` prop is forwarded — useful for staggered lists where each child needs its own offset or delay. Children without `custom` inherit the nearest motion ancestor's value.
```svelte
<script lang="ts">
import { motion, type Variants } from '@humanspeak/svelte-motion'
const variants: Variants = {
hidden: { opacity: 0, x: -100 },
visible: (i) => ({
opacity: 1,
x: 0,
transition: { delay: (i as number) * 0.1 }
})
}
const items = ['Alpha', 'Beta', 'Gamma', 'Delta']
</script>
{#each items as item, i}
<motion.li custom={i} {variants} initial="hidden" animate="visible">
{item}
</motion.li>
{/each}
```
## Layout animation
Single-element FLIP layout animation:
```svelte
<motion.div layout />
<motion.div layout="position" />
```
- `layout`: translate + scale.
- `layout="position"`: translate only.
- Shared layout (`layoutId`) is not implemented yet.
## Utilities
- `useAnimationFrame`
- `useMotionTemplate`
- `useSpring`
- `useTime`
- `useTransform`
- `useVelocity`
- `styleString`
- `stringifyStyleObject` (deprecated)
- `createDragControls`
The package also re-exports core helpers from `motion` (for example `animate`, `stagger`, `transform`, easings, and utility functions).
## SSR behavior
- Initial visual state is rendered server-side from `initial` (or first `animate` keyframe when `initial` is empty).
- `initial={false}` skips initial enter animation.
- Hydration path is designed to avoid flicker.
## Verification snapshot
Validated against current source and test suite (local run):
- Unit/component tests: `259 passed`
- E2E tests: `78 passed`, `1 skipped`
## Known gaps vs Framer Motion
- No pan gesture API (`whilePan`, `onPan*`).
- `whileInView` does not yet expose Framer-style viewport options.
- `MotionConfig` currently only provides `transition` defaults.
- `reducedMotion`, `features`, and `transformPagePoint` are not implemented.
## External dependencies
- `motion`
- `motion-dom`
## License
MIT © [Humanspeak, Inc.](LICENSE)
## Credits
Made with ❤️ by [Humanspeak](https://humanspeak.com)