layoutDependency

layoutDependency tells a layout element when to re-measure. By default a layout element recomputes its FLIP box on every render that touches its class, style, layoutId, or transition. When you pass layoutDependency, measurement is gated on only that value — the element stops re-measuring on unrelated renders and only snapshots when the dependency changes.

It’s a performance optimization for layout elements that re-render often but rarely change box.

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

  // `order` changes only when the list is actually reordered.
  let { order } = $props()
</script>

<motion.div layout layoutDependency={order} />
<script lang="ts">
  import { motion } from '@humanspeak/svelte-motion'

  // `order` changes only when the list is actually reordered.
  let { order } = $props()
</script>

<motion.div layout layoutDependency={order} />
mode · live running open
// no gate re-measures every render

0 measures

A
// layoutDependency measures only on dep change

0 measures

B

In the example above, both boxes re-render constantly (their color cycles). The left box re-measures on every render — its measure counter climbs. The right box passes layoutDependency={dep}, so its counter stays flat until Reflow bumps the dependency.

When to use it

Reach for layoutDependency when a layout element:

  • re-renders frequently (live text, streaming values, a ticking clock), and
  • only changes layout box on a specific, known signal.

Pass a value that changes exactly when the layout should be re-measured — commonly a counter, a sort key, or the element’s position in a list.

Any defined value gates — including falsy ones like 0, '', or null. Leave it undefined (the default) to keep framer-motion’s “measure on every render” behavior.

What the gate blocks

While layoutDependency is set, only a change to that value re-measures the element. This matches upstream MeasureLayout, which snapshots when drag is set, the dependency changed, or the element’s own presence flipped — and never otherwise. Layout changes that arrive any other way do not animate a gated element, and don’t fire its onProjectionUpdate / onLayoutMeasure; it jumps to its new slot:

  • a sibling reorder that re-slots it,
  • a resize or a parent style change that moves it,
  • a sibling’s AnimatePresence enter/exit,
  • an AnimatePresence mode="wait" child swap inside a gated layout parent (that’s the child’s presence, not the parent’s).

Two escape hatches, both from upstream: drag opts the element out of gating entirely, and the element’s own isPresent flip (an owned <AnimatePresence present={…}>{#snippet child()}… exit or re-entry) still snapshots it.

What the gate costs

The parity is behavioral. Upstream can skip a gated node entirely because React lets it snapshot before the DOM commits; Svelte reconciles a keyed {#each} before a child can look. So a gated element still refreshes its cached slot with one silent DOM read when an observed change re-slots it (no snapshot, no FLIP, no callbacks) — that cached slot is the origin its next dependency-driven FLIP animates from. Unrelated renders cost a gated element nothing. Observers are shared per parent/ancestor, so a 1,000-row list runs one observer per target, not one per row.

Keyed lists

In a keyed {#each} the dependency must change for every row that moves. Bind it to the row’s index (or sort position), not to a field of the row’s data:

{#each rows as row, i (row.id)}
  <!-- ✅ index changes for exactly the rows whose slot changed -->
  <motion.li layout="position" layoutDependency={i}>{row.label}</motion.li>
{/each}
{#each rows as row, i (row.id)}
  <!-- ✅ index changes for exactly the rows whose slot changed -->
  <motion.li layout="position" layoutDependency={i}>{row.label}</motion.li>
{/each}
{#each rows as row (row.id)}
  <!-- ❌ only the row whose timestamp changed animates; the rows it
       displaces have an unchanged dependency and jump -->
  <motion.li layout="position" layoutDependency={row.updatedAt}>{row.label}</motion.li>
{/each}
{#each rows as row (row.id)}
  <!-- ❌ only the row whose timestamp changed animates; the rows it
       displaces have an unchanged dependency and jump -->
  <motion.li layout="position" layoutDependency={row.updatedAt}>{row.label}</motion.li>
{/each}

If you don’t need the gate, simply omit layoutDependency and every row animates on every reorder.

API Reference

layoutDependency

layoutDependency?: unknown
layoutDependency?: unknown

Pass any value to a layout motion.* component. While it is defined, measurement is gated on changes to this value; while it is undefined, measurement follows the default render-driven behavior. Requires layout to be enabled.

Based on Motion’s layoutDependency prop.