<!-- Source: https://motion.svelte.page/docs -->

# Get started with Svelte Motion

> Svelte Motion is a Svelte animation library for building smooth, production-grade UI animations.

**Source:** [https://motion.svelte.page/docs](https://motion.svelte.page/docs)

---

**Svelte Motion** is a Svelte animation library for building smooth, production-grade UI animations. You can start with simple prop-based animations before growing to layout, gesture and scroll animations.

Svelte Motion's unique **hybrid engine** combines the performance of native browser animations with the flexibility of JavaScript. It's designed to feel like a natural extension of Svelte's reactive philosophy.

In this guide, we'll learn **why** and **when** you should use Svelte Motion, how to **install** it, and then take a whirlwind tour of its main features.

## Why Svelte Motion?

Svelte gives you the power to build dynamic user interfaces with excellent performance out of the box. Svelte Motion extends this power to animations, making it simple to create everything from beautiful micro-interactions to complex, gesture-driven animations.

```svelte
<script lang="ts">
    import { motion } from '@humanspeak/svelte-motion'
</script>

<motion.button
    class="my-4 rounded-md border border-border bg-blue-300 px-4 py-2 text-text-primary"
    animate={{ opacity: 1 }}
    initial={{ opacity: 0 }}
    transition={{ duration: 0.6 }}
>
    Animated button
</motion.button>

```

> **Using component-scoped CSS?** If you replace a styled native element like
> `<h2 class="headline">` with `<motion.h2 class="headline">`, Svelte may treat
> `.headline` as unused because the class is now passed to a component. Use
> [`scoped:class` with @humanspeak/svelte-scoped-props](/docs/scoped-motion-classes)
> when you need parent-scoped CSS to survive that boundary.

> Live example: [/examples/animated-button](https://motion.svelte.page/examples/animated-button)

### Key advantages

Here's when it's the right choice for your project.

- **Built for Svelte.** Sometimes finding examples for animations with Svelte can be tricky. Svelte Motion is attempting to provide an exact copy of the Motion for React API which there is a lot of examples for out there in the wild.
- **Animate anything.** CSS has hard limits. There are values you can't animate, keyframes you can't interrupt, staggers that must be hardcoded. Svelte Motion provides a single, consistent API that handles everything from simple transitions to advanced scroll, layout, and gesture-driven effects.
- **App-like gestures.** Standard CSS `:hover` events can be unreliable on touch devices. Svelte Motion provides robust, cross-device gesture recognizers for tap and hover.

### When is CSS a better choice?

For simple, self-contained effects (like a color change on hover) a standard CSS transition is a lightweight solution. The strength of Svelte Motion is that it can do these simple kinds of animations but also scale to anything you can imagine—using the same easy to write and maintain API.

## Install

Svelte Motion is available via npm:

```bash
npm install @humanspeak/svelte-motion
```

Features can now be imported:

```js
import { motion } from '@humanspeak/svelte-motion'
```

## Your first animation

The `<motion>` component is the core API in Svelte Motion. It's a DOM element, supercharged with animation capabilities.

```svelte
<script>
  import { motion } from '@humanspeak/svelte-motion';
</script>

<motion.ul
    class="size-24 rounded-md bg-pink-400"
    animate={{ rotate: 360 }}
    transition={{ duration: 1 }}
/>
```

> Live example: [/examples/rotate](https://motion.svelte.page/examples/rotate)

When values in `animate` change, the component will animate. Svelte Motion has intuitive defaults, but animations can of course be configured via the `transition` prop.

```svelte
<motion.div
  animate={{
    scale: 2,
    transition: { duration: 2 }
  }}
>
  Scaling div
</motion.div>
```

## Enter animation

When a component enters the page, it will automatically animate to the values defined in the `animate` prop.

You can provide values to animate from via the `initial` prop (otherwise these will be read from the DOM).

```svelte
<motion.button initial={{ scale: 0 }} animate={{ scale: 1 }}>
  Scaling button
</motion.button>
```

Or disable this initial animation entirely by setting `initial` to `false`.

```svelte
<motion.button initial={false} animate={{ scale: 1 }}>
  No initial animation
</motion.button>
```

## Hover & tap animation

`<motion>` extends Svelte's event system with powerful gesture animations. It currently supports hover and tap.

```svelte
<motion.button
  whileHover={{ scale: 1.2 }}
  whileTap={{ scale: 0.8 }}
>
  Interactive button
</motion.button>
```

> Live example: [/examples/hover-and-tap](https://motion.svelte.page/examples/hover-and-tap)

## Scroll animation

Svelte Motion supports scroll-triggered animations via the `whileInView` prop.

Elements can animate when they enter the viewport:

```svelte
<motion.div
  initial={{ opacity: 0, y: 50 }}
  whileInView={{ opacity: 1, y: 0 }}
>
  I animate when scrolled into view
</motion.div>
```

> Live example: [/examples/while-in-view](https://motion.svelte.page/examples/while-in-view)

The `whileInView` prop works like `whileHover` and `whileTap`—it defines the target animation state when the element is in the viewport. When the element leaves the viewport, it animates back to its baseline state.

You can also use lifecycle callbacks:

```svelte
<motion.div
  whileInView={{ opacity: 1 }}
  onInViewStart={() => console.log('Entered viewport')}
  onInViewEnd={() => console.log('Left viewport')}
/>
```

Configure the underlying IntersectionObserver with the `viewport` prop:

```svelte
<motion.div
  initial={{ opacity: 0, y: 50 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, amount: 0.5, margin: '-50px' }}
/>
```

- `once: true` — fire entry once, latch, never animate back
- `amount` — `number` (0-1), `'some'` (any pixel), or `'all'` (fully visible)
- `margin` — CSS margin string applied to the IntersectionObserver root box
- `root` — custom IntersectionObserver root (defaults to the viewport)

For scroll-linked animations (where values track scroll position), use utilities like `useTime`/`useTransform`:

```svelte
<script>
  import { motion, useTime, useTransform } from '@humanspeak/svelte-motion'
  const time = useTime()
  const progress = useTransform(() => ($time % 4000) / 4000, [time])
</script>

<motion.div style={`transform: scaleX(${$progress})`}/>
```

## Layout animation

Animate between changes in layout using transforms. It's as easy as applying the `layout` prop.

```svelte
<motion.div layout />
```

- `layout` animates translation and scale between layout changes.
- `layout="position"` animates translation only.
- `layoutId` animates between different elements that share the same identifier (shared layout animations).

See [Layout Animations](/docs/layout-animations) for the full guide.

## Exit animations

By wrapping motion components with `AnimatePresence` you gain access to exit animations. This allows you to animate elements as they're removed from the DOM. ([Reference](https://motion.dev/docs/react))

```svelte
<script>
  import { motion, AnimatePresence } from '@humanspeak/svelte-motion'
  let show = $state(true)
</script>

<AnimatePresence>
  {#if show}
    <motion.div
      initial={{ opacity: 0, scale: 0.9 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.6 } }}
      transition={{ duration: 0.4 }}
      class="size-24 rounded-md bg-cyan-400"
    />
  {/if}
</AnimatePresence>

<motion.button whileTap={{ scale: 0.97 }} on:click={() => (show = !show)}>
  Toggle
</motion.button>
```

Transition precedence for exit timing (merged):

- `exit.transition` (highest precedence)
- component `transition` (merged with any `MotionConfig` defaults)
- fallback `{ duration: 0.35 }`

## Variants

Variants let you give names to animation states and reference them by string. Pair them with the `custom` prop for per-instance values — common for staggered lists where each child needs its own offset.

```svelte
<script>
  import { motion } from '@humanspeak/svelte-motion'

  const variants = {
    hidden:  { opacity: 0, x: -100 },
    visible: (i) => ({
      opacity: 1,
      x: 0,
      transition: { delay: i * 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}
```

- A variant entry can be a static keyframes object **or** a `(custom) => keyframes` factory.
- `custom` is forwarded to the factory each time the variant is resolved (initial, animate, or exit).
- Children with no `custom` prop inherit the nearest motion ancestor's value — matching Framer Motion's variant-tree propagation.

## Learn next

That's a quick overview of Svelte Motion's basic features. Next, explore animation patterns, layout, and gestures—or dive straight into our examples.
