The motion entry point is deliberately small and stable. Import every public motion primitive from fractalsvelte/motion.
Nested presence
Nested content
Drag and drop
Automatic layout
<script lang="ts">
import {
LayoutGroup,
Motion,
MotionProvider,
AnimatePresence,
ReorderGroup,
ReorderItem,
SharedLayout,
createDragControls,
motionIntents,
presets,
transitions
} from 'fractalsvelte/motion';
</script>
Layout and stateful component motion
The library now applies lifecycle continuity to Accordion content and Tree View child groups. Accordion panels and expanded tree branches remain mounted while their exit animation completes; during that period they are aria-hidden and inert.
Tabs use a single persistent selection indicator. When a tab changes, that indicator FLIP-animates from the previous trigger’s position and width to the next one using the snappy spring. This is a scoped shared-layout pattern: every Tabs instance owns its own indicator, so two tab lists never cross-animate.
Grid and App Shell animate their layout changes with a 200ms CSS transition. Grid transitions columns and gap; App Shell transitions its sidebar grid columns. Both disable that transition under prefers-reduced-motion.
Use LayoutGroup and SharedLayout when one visual element moves between conditional mount points. A SharedLayout captures its last rectangle when it unmounts, then the next element with the same layoutId in the same group animates from that rectangle using translate and scale. Set persist on a named group when the two mount points live on consecutive routes; the rectangle is held in session storage only until its next matching destination consumes it.
<LayoutGroup id="workspace-selection" persist>
{#each tabs as tab (tab)}
<button onclick={() => (selected = tab)}>
{tab}
{#if selected === tab}
<SharedLayout layoutId="tab-indicator" class="indicator" />
{/if}
</button>
{/each}
</LayoutGroup>
Keep only one mounted SharedLayout for a given layoutId within a group. The primitive is for shared visual decoration such as tab underlines, selected pills, and moving highlights; it does not transfer focus, content, or event handlers between elements.
Layout automatically FLIP-animates an element when its own size, its parent’s children/attributes, or an ancestor scroll position changes. layoutKey remains available when application state gives the clearest measurement boundary. Use mode="position" when child content must not scale. A new layout target interrupts and retargets the old animation instead of competing with it.
AnimatePresence keeps a conditional region mounted through exit; its wrapper becomes aria-hidden and inert as soon as present is false. Its mode="sync" default permits immediate re-entry; mode="wait" completes the current exit before re-entering. Nested instances coordinate their exits before their parent unmounts. Pass custom for descendant presence data and returnFocus for an explicit post-exit focus target.
Gestures and motion values
Motion accepts whileHover, whileTap, whileFocus, whilePan, and whileInView targets. It exposes hover, tap, pan, and focus callbacks. Hover is gated to fine hover-capable pointers; Enter and Space receive the same tap lifecycle as pointer activation. whileInView accepts native observer options through ininview.
The public utility layer exports useMotionValue, useSpring, useTransform, useAnimate, useScroll, useInView, and useMotionValueEvent. For Svelte-rune code, use useMotionValueState, useVelocityState, deriveMotionValue, motionTemplate, motionStyle, motionCssVars, useInViewState, and useScrollProgress. The reactive helpers release subscriptions automatically with their owning component; derived values created outside a component provide destroy().
Reorder
ReorderGroup and ReorderItem provide pointer and keyboard reordering for linear, grid, and nested collections. The group owns the ordered values; each item declares the value it represents. Keep the {#each} key equal to the item value. Nested groups are independently scoped by their nearest parent group.
<script lang="ts">
import { ReorderGroup, ReorderItem } from 'fractalsvelte/motion';
let tasks = $state(['Research', 'Prototype', 'Review']);
</script>
<ReorderGroup bind:values={tasks} axis="y">
{#each tasks as task (task)}
<ReorderItem value={task}>{task}</ReorderItem>
{/each}
</ReorderGroup>
Drag a task by its row to reorder the bound array.
Order: Research → Prototype → Review → Ship
| API | Purpose | Current behaviour |
|---|---|---|
ReorderGroup | Owns values, axis, and collection state | axis is y by default; supports x and both; emits onreorder and supports bind:values. |
ReorderItem | Pointer-draggable entry | Reorders at sibling midpoints and FLIP-animates displaced siblings with the group’s transition. |
transition | Reorder settling transition | Defaults to transitions.snappy; respects the provider’s reduced-motion setting. |
Set axis="both" for a grid; the closest cell becomes the reorder target. For nested collections, give each nested collection its own ReorderGroup and value array. Give independently owned lists the same group and distinct id values to permit a pointer drop transfer; accepts controls target eligibility and ontransfer receives the source, target, item, and insertion index. Use DataTable’s purpose-built row ordering for sortable tabular data.
Drag, drop, and collection motion
Drag provides constrained x/y/both-axis pointer movement, optional return-to-origin, elastic constraint overflow, momentum, velocity-aware callbacks, and ancestor auto-scroll at container edges. Use createDragControls() for a dedicated drag handle. Pair it with DropZone for payload-aware drop targets; a drop zone only activates when its accept predicate permits the payload.
DropZone also handles native file drops. Use acceptFiles with MIME rules (image/*), exact MIME types, extensions (.csv), or a predicate. Accepted and rejected file sets are reported separately, and position="auto" resolves a before/inside/after placement from the current pointer.
ReorderItem also supports keyboard ordering: focus an item, press Space or Enter to pick it up, use the relevant arrow keys to move it, and press Escape to cancel. DataTable accepts reorderable plus onreorder for native table-row ordering when the table is not sorted.
<Drag payload={task} axis="y" constraints={{ top: -80, bottom: 80 }}>
<Task {task} />
</Drag>
<DropZone accept={(payload) => isTask(payload)} ondrop={(task) => archive(task)}>Archive</DropZone>
Public API reference
| Item | Use it for | Not intended for |
|---|---|---|
Motion | Keyframes plus hover, press, focus, pan, and in-view targets | Exit presence, drag, layout projection, or variant propagation. |
AnimatePresence / Layout | Conditional exit coordination and automatic one-element FLIP layout | Cross-route content/focus transfer. |
MotionProvider | A subtree’s reduced-motion policy and <Motion> default transition | Retiming built-in component animations. |
motionTokens / motionIntents | Semantic timing, delays, stagger, and component-intent transitions | A semantic replacement for component state. |
transitions | instant, fast, standard, emphasized, gentle, and snappy | A global CSS transition reset. |
LayoutGroup / SharedLayout | FLIP transitions between conditional visual elements | Focus/content handoff or arbitrary cross-page shared elements. |
Drag / DropZone | Controlled drag, native files, payload targets, and placement cues | Cross-window drag transfer. |
ReorderGroup / ReorderItem | Pointer/keyboard linear and grid ordering plus same-group transfers | Arbitrary tree move semantics between distinct data structures. |
Motion authoring rules
Use motionIntents.feedback for acknowledgement, disclosure for compact reveal/hide, selection for a changing active choice, overlay for modal surfaces, navigation for a route-level change, layout for structural movement, and reorder for direct manipulation. Use motionTokens.delay and stagger() for sequential content; do not invent per-component timing constants. Every custom motion path must retain an understandable opacity/semantic path when reduced motion removes transforms.
