arc()

arc() creates a quadratic curved path for simultaneous x and y motion. Pass the returned path to transition.path; the same API works with keyframes, layout animations, shared layoutId transitions, and imperative animation.

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

  const path = arc()
</script>

<motion.div
  animate={{ x: 200, y: 0 }}
  transition={{ duration: 1, path }}
/>
<script lang="ts">
  import { arc, motion } from '@humanspeak/svelte-motion'

  const path = arc()
</script>

<motion.div
  animate={{ x: 200, y: 0 }}
  transition={{ duration: 1, path }}
/>
mode · live running open
// transition.path strength 0.5 · auto
strength
click the stage quadratic arc

Options

OptionDefaultDescription
strength0.5Controls the bend. 1 peaks at a height equal to the distance between the two points; 0 has no bend.
peak0.5Sets where along the bend the arc reaches maximum height. 0.5 is symmetric; lower values move the peak toward the start.
directionautoLock the bend to "cw" or "ccw" relative to the direction of travel. Auto keeps the bulge on a stable screen-space side.
rotatefalsetrue follows the full path tangent. A number from 0 to 1 scales the tangent rotation.
const path = arc({
  strength: 0.8,
  peak: 0.35,
  direction: 'cw',
  rotate: true
})
const path = arc({
  strength: 0.8,
  peak: 0.35,
  direction: 'cw',
  rotate: true
})

Layout animations

Put the path inside the layout transition for FLIP and shared layoutId animations:

<motion.div
  layout
  transition={{
    layout: { duration: 0.6, path: arc({ strength: 0.6 }) }
  }}
/>
<motion.div
  layout
  transition={{
    layout: { duration: 0.6, path: arc({ strength: 0.6 }) }
  }}
/>

The same transition shape works on both ends of a shared layoutId change. Motion skips the arc for layout shifts under 20px, where a curved detour would be visually noisy.

Springs

The path changes geometry, not timing, so it composes with spring transitions:

<motion.div
  animate={{ x: 200, y: 100 }}
  transition={{
    type: 'spring',
    bounce: 0.5,
    path: arc({ strength: 1 })
  }}
/>
<motion.div
  animate={{ x: 200, y: 100 }}
  transition={{
    type: 'spring',
    bounce: 0.5,
    path: arc({ strength: 1 })
  }}
/>

Imperative animation

Use the same path with animate() or with useAnimate():

import { animate, arc } from '@humanspeak/svelte-motion'

animate(element, { x: 200, y: 100 }, {
  duration: 1,
  path: arc()
})
import { animate, arc } from '@humanspeak/svelte-motion'

animate(element, { x: 200, y: 100 }, {
  duration: 1,
  path: arc()
})

Rotation

rotate: true turns the element along the path tangent; a number between 0 and 1 applies a scaled amount. Path rotation is additive to the element’s own rotate, so a concurrent rotation animation is preserved rather than overwritten.

const path = arc({ rotate: true })
const subtlePath = arc({ rotate: 0.35 })
const path = arc({ rotate: true })
const subtlePath = arc({ rotate: 0.35 })

Numeric x/y only

arc() bends numeric x/y values (pixels). Unit-bearing endpoints — '100px', '50%', 'var(--x)' — are valid everywhere else in the API, but upstream Motion’s arc does its bezier math on raw numbers, so they would produce NaN mid-flight. Svelte Motion guards that case: when either endpoint is non-numeric the path steps aside, x/y animate along the default straight line, and a one-time console.warn explains why in development builds.

<!-- curves -->
<motion.div animate={{ x: 200 }} transition={{ path }} />

<!-- straight line + dev warning: '50%' is not a number -->
<motion.div animate={{ x: '50%' }} transition={{ path }} />
<!-- curves -->
<motion.div animate={{ x: 200 }} transition={{ path }} />

<!-- straight line + dev warning: '50%' is not a number -->
<motion.div animate={{ x: '50%' }} transition={{ path }} />

Layout animations (transition.layout.path) are unaffected — projection always works in pixel deltas.

Post-1.x follow-up: the numeric guard mirrors an upstream limitation, not a design choice. Once Motion decides how arc() should resolve unit-bearing endpoints, this wrapper either adopts their resolution or is removed — revisit after the 1.x line.

Reuse the instance

Create one arc() instance for all animations on the same component. The path keeps continuity state so automatic direction stays on a stable screen-space side across reversals.

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

  const path = arc()
</script>

<!-- Reuse `path`; don't call arc() inline in the template. -->
<motion.div animate={{ x }} transition={{ path }} />
<script lang="ts">
  import { arc, motion } from '@humanspeak/svelte-motion'

  const path = arc()
</script>

<!-- Reuse `path`; don't call arc() inline in the template. -->
<motion.div animate={{ x }} transition={{ path }} />

In Svelte, an inline transition={{ path: arc() }} can be re-evaluated whenever a reactive dependency in that expression changes, discarding the continuity state.