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} />0 measures
0 measures
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
AnimatePresenceenter/exit, - an
AnimatePresence mode="wait"child swap inside a gatedlayoutparent (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?: unknownlayoutDependency?: unknownPass 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.