Motion for Svelte 5, one attachment at a time.
svelte-attach-motion wraps Motion's animation
engine in Svelte attachments. No wrapper components,
no <motion.div>. Add animation to the elements you already have, with
automatic cleanup and reduced-motion support built in.
npm install svelte-attach-motion motion animate
Animates an element when it mounts. Pass an array to set start and end values.
<div data-reveal {@attach animate(
{ opacity: [0, 1], y: [24, 0] },
{ transition: { duration: 0.5 } }
)} /> animate + $state
Attachments re-run when the state they read changes, so the element animates to each new value from wherever it currently is.
<script>
let on = $state(false);
</script>
<button onclick={() => (on = !on)}>
<span {@attach animate(
{ x: on ? 40 : 0 },
{ transition: { type: 'spring', bounce: 0.35 } }
)} />
</button> hover
Animates while a mouse hovers the element and returns to its resting values afterwards. Touch input is ignored, so there are no "stuck" hover states on phones.
<article {@attach hover(
{ y: -6, boxShadow: '0 12px 24px rgb(0 0 0 / 0.15)' }
)}>…</article> press
Animates while the element is pressed, by pointer or keyboard. Try focusing the button and holding Enter.
<button {@attach press({ scale: 0.92 })}>Save draft</button> inView
Animates when the element scrolls into view. Use initial for the hidden state and once: false to replay every time.
- Inbox
- Drafts
- Shared with me
- Archive
- Trash
<li data-reveal {@attach inView(
{ opacity: 1, x: 0 },
{ initial: { opacity: 0, x: -32 }, once: false, amount: 0.6 }
)}>…</li> scrollAnimate
Links an animation to scroll position. The shape rotates as it travels through the viewport. Scroll-linked motion is skipped entirely when the user prefers reduced motion.
<div {@attach scrollAnimate(
{ rotate: [-12, 12], scale: [0.8, 1.1] },
{ offset: ['start end', 'end start'] }
)} /> Reduced motion by default
Every attachment respects prefers-reduced-motion. Animations still reach their
final state, so nothing is left hidden, but instantly. Try it on the demos above:
<div {@attach animate(keyframes, { reducedMotion: 'user' })} />
<!-- 'user' (default) | 'always' | 'never' --> No flash on server-rendered pages
Attachments only run once JavaScript loads, so a prerendered element would first appear, then
hide, then animate in. Import reveal.css once and add data-reveal to entrance
animations: the element starts hidden only when the browser can run scripts, the attachment takes
over when it mounts, and a CSS fail-safe shows it anyway if the JavaScript never arrives.
<!-- +layout.svelte -->
<script>
import 'svelte-attach-motion/reveal.css';
</script>
<!-- any entrance animation on a server-rendered page -->
<h1 data-reveal {@attach animate({ opacity: [0, 1], y: [12, 0] })}>…</h1>