Motion() Factory

Complete reference for the Motion() function — retrieve, create, and configure timelines.

Motion() is the single entry point for every animation in the SDK. Call it to create a named timeline, retrieve an existing one, or build a multi-step sequence — then chain a trigger to activate it.


Import

typescript
import { Motion } from "@motion.page/sdk";

No-bundler environments expose Motion as a global on window:

html
<script src="https://cloud.motion.page/sdk/latest.js"></script>
<script>
  // window.Motion is available globally
  Motion("fade", ".box", { from: { opacity: 0 }, duration: 0.6 }).onPageLoad();
</script>

Function Signatures

Motion has three call signatures:

typescript
// 1. Retrieve an existing timeline by name
Motion(name: string): Timeline

// 2. Single-animation timeline — one target, one config object
Motion(name: string, target: TargetInput, config: AnimationConfig): Timeline

// 3. Multi-step timeline — array of animation entries
Motion(name: string, animations: AnimationEntry[]): Timeline

The name is a registry key. The same name always returns the same Timeline instance, anywhere in your code. Passing target and config with an already-registered name appends to that timeline rather than replacing it — see Appending Warning.


Basic Usage

typescript
import { Motion } from "@motion.page/sdk";

// Create and play immediately
Motion("hero", "#hero", {
  from: { opacity: 0, y: 40 },
  duration: 0.8,
  ease: "power2.out",
}).play();

// Create and play on page load
Motion("cards", ".card", {
  from: { opacity: 0, y: 30 },
  duration: 0.6,
  stagger: 0.08,
  ease: "power2.out",
}).onPageLoad();

// Retrieve an existing timeline later by name
const tl = Motion("hero");
tl.pause();


TargetInput

The target parameter (overload 2) and the target field in each AnimationEntry (overload 3) accept a TargetInput:

TypeExample
CSS selector string"#hero", ".card", "[data-animate]"
string[]["#title", "#subtitle"]
Elementdocument.querySelector(".box")
NodeListdocument.querySelectorAll(".item")
Element[]Array of DOM elements
Plain object{ value: 0 } — for non-DOM tweening
Array of plain objects[{ count: 0 }, { count: 100 }]

CSS selectors are the most common form. Plain objects are useful for tweening arbitrary JS values — for example, driving a canvas counter or animating a custom data property:

typescript
const counter = { value: 0 };

Motion("count-up", counter, {
  to: { value: 1000 },
  duration: 2,
  ease: "power2.out",
  onUpdate: () => {
    document.querySelector("#counter")!.textContent =
      Math.round(counter.value).toString();
  },
}).onPageLoad();

AnimationConfig

The config object passed to overload 2, and embedded in each AnimationEntry in overload 3.

OptionTypeDefaultDescription
fromAnimationVarsStart values. SDK reads current CSS as to when only from is specified
toAnimationVarsEnd values. SDK reads current CSS as from when only to is specified
durationnumber0.5Animation duration in seconds
delaynumber0Seconds to wait before the animation starts
easestring"power1.inOut"Easing curve — e.g. "power2.out", "elastic.out"
staggernumber | StaggerVarsPer-element delay when target matches multiple elements
repeatnumber | RepeatConfigRepeat count for this animation. -1 = infinite
splitSplitTypeSplit text into chars, words, or lines before animating
maskbooleanfalseWraps split elements in overflow: hidden containers for reveal effects
flapFlapConfigSplit-flap / character-scramble effect. Requires split: 'chars'
fitFitConfigFLIP-style morph between two elements. Overrides from/to
axis'x' | 'y'Axis binding for .onMouseMove() trigger
onStart() => voidFired when the animation begins
onUpdate(progress: number) => voidFired on every frame with current progress (0–1)
onComplete() => voidFired when the animation finishes
onRepeat(repeatCount: number) => voidFired after each repeat cycle
onReverseComplete() => voidFired when animation completes in reverse

Full TypeScript shape:

typescript
interface AnimationConfig {
  from?:              AnimationVars;
  to?:                AnimationVars;
  duration?:          number;
  delay?:             number;
  ease?:              string;
  stagger?:           number | StaggerVars;
  repeat?:            number | RepeatConfig;
  split?:             SplitType;
  mask?:              boolean;
  flap?:              FlapConfig;
  fit?:               FitConfig;
  axis?:              'x' | 'y';
  onStart?:           () => void;
  onUpdate?:          (progress: number) => void;
  onComplete?:        () => void;
  onRepeat?:          (repeatCount: number) => void;
  onReverseComplete?: () => void;
}

AnimationVars

All properties that can appear in from and to blocks.

Transforms

PropertyTypeNotes
xnumber | stringTranslate X — pixels by default (50 = 50px)
ynumber | stringTranslate Y — pixels by default
znumber | stringTranslate Z — pixels by default
rotatenumber | stringRotation in degrees (90 = 90deg)
rotateXnumber | string3D rotation around X axis
rotateYnumber | string3D rotation around Y axis
rotateZnumber | string3D rotation around Z axis (same as rotate)
scalenumberUniform scale — unitless (1.5 = 150%)
scaleXnumberHorizontal scale
scaleYnumberVertical scale
scaleZnumberDepth scale
skewXnumber | stringHorizontal skew in degrees
skewYnumber | stringVertical skew in degrees
perspectivenumber | stringCSS perspective depth
transformOriginstringTransform origin — e.g. "50% 50%", "top left", "0% 100%"

Visual

PropertyTypeNotes
opacitynumber0 (transparent) to 1 (fully visible)
backgroundColorstringAny CSS color string
colorstringText / foreground color
filterstringCSS filter string — e.g. "blur(4px) brightness(0.8)"
clipPathstringCSS clip-path shape function (camelCase form)
'clip-path'stringCSS clip-path shape function (kebab-case, must be quoted)
boxShadowstringCSS box-shadow string
borderRadiusnumber | stringBorder radius — px or string
borderColorstringBorder color
outlineColorstringOutline color

Layout

PropertyTypeNotes
widthnumber | stringElement width — px or string with unit
heightnumber | stringElement height — px or string with unit
topnumber | stringCSS top
leftnumber | stringCSS left
rightnumber | stringCSS right
bottomnumber | stringCSS bottom
marginstringShorthand margin
paddingstringShorthand padding
zIndexnumberStacking order
backgroundPositionstringBackground position

Typography

PropertyTypeNotes
fontSizenumber | stringFont size
lineHeightnumber | stringLine height
letterSpacingnumber | stringLetter spacing

SVG

PropertyTypeNotes
fillstringSVG fill color
strokestringSVG stroke color
drawSVGstring | DrawSVGObjectAnimate SVG stroke — see drawSVG

Motion Path

typescript
path: {
  target:   string | Element;  // CSS selector, Element, or raw path data (M/m commands)
  align?:   string | Element;  // Align bounding box to this element
  alignAt?: [number, number];  // Origin point [x%, y%]; default [50, 50]
  start?:   number;            // Path start fraction 0–1; default 0
  end?:     number;            // Path end fraction 0–1; default 1
  rotate?:  boolean;           // Auto-rotate along the path tangent
}

CSS Custom Properties

Any CSS custom property — prefix with --:

typescript
to: { '--accent-color': '#9966FF', '--spacing': '24px' }

from / to Resolution

The SDK auto-resolves the missing endpoint from the element’s computed CSS at build time. Specify only what’s different from the natural state.

ConfigBehavior
from onlySDK reads current CSS as to. Start at custom values, animate into natural state — use for reveals
to onlySDK reads current CSS as from. Start at natural state, animate to custom values — use for hover/exit effects
BothBoth endpoints explicit — use when neither endpoint matches the element’s natural CSS

Natural CSS defaults — the SDK treats these as the element’s rest state. Omit them from to when they’re the target endpoint:

opacity: 1 · x: 0 · y: 0 · z: 0 · scale: 1 · scaleX: 1 · scaleY: 1 · rotate: 0

typescript
// ✅ from-only — SDK fills in opacity:1, y:0 from the element's CSS
Motion("reveal", ".card", {
  from: { opacity: 0, y: 50 },
  duration: 0.6,
}).onPageLoad();

// ✅ to-only — start at natural state, hover to raised state
Motion("lift", ".card", {
  to: { y: -8, boxShadow: "0 12px 24px rgba(0,0,0,0.15)" },
  duration: 0.3,
}).onHover({ each: true, onLeave: "reverse" });

// ✅ both — parallax where neither endpoint is the natural state
Motion("parallax", ".bg", {
  from: { y: -60 },
  to:   { y: 60 },
}).onScroll({ scrub: true });

// ❌ redundant — opacity:1 and y:0 are natural defaults
Motion("bad", ".card", {
  from: { opacity: 0, y: 50 },
  to:   { opacity: 1, y: 0 },
}).onPageLoad();

Multi-Step Timelines

Pass an array of AnimationEntry objects as the second argument to build coordinated sequences across multiple targets.

typescript
Motion("intro", [
  { target: ".hero-title",    from: { opacity: 0, y: 40 }, duration: 0.7, ease: "power2.out" },
  { target: ".hero-subtitle", from: { opacity: 0, y: 30 }, duration: 0.6, ease: "power2.out" },
  { target: ".hero-cta",      from: { opacity: 0, y: 20 }, duration: 0.5, ease: "power2.out" },
]).onPageLoad();

By default, each step starts immediately after the previous one ends. Control timing with the position field.

AnimationEntry

Every entry extends AnimationConfig with two additional fields:

FieldTypeDescription
targetTargetInputRequired. CSS selector, Element, NodeList, or Element[]
positionnumber | stringWhen in the timeline this step starts. Omit for sequential playback
typescript
interface AnimationEntry extends AnimationConfig {
  target:    TargetInput;
  position?: number | string;
}

Position Syntax

Used in AnimationEntry.position, tl.call(), and scroll trigger end values.

ValueMeaning
0.5Absolute — 0.5 s from the timeline start
"+=0.5"0.5 s after the previous entry ends
"-=0.3"0.3 s before the previous entry ends (overlap)
">"Immediately after the previous entry ends (same as default)
"<"Same start time as the previous entry (parallel)
"<0.2"0.2 s after the start of the previous entry
">-0.1"0.1 s before the end of the previous entry
typescript
Motion("hero", [
  // Title and image start at the same time (parallel)
  { target: ".title", from: { opacity: 0, y: 40 }, duration: 0.8, ease: "power3.out" },
  { target: ".image", from: { opacity: 0, scale: 0.9 }, duration: 0.8, ease: "power2.out", position: "<" },
  // Badge appears 0.3 s after both start
  { target: ".badge", from: { opacity: 0, scale: 0 }, duration: 0.4, ease: "back.out", position: "<0.3" },
  // Footer enters with a gap after everything else
  { target: ".footer", from: { opacity: 0 }, duration: 0.4, position: "+=0.2" },
]).onPageLoad();

Timeline Object

Every Motion() call returns a Timeline instance with the following methods.

Playback

typescript
tl.play(from?: number): this       // Play forward; optional start time in seconds
tl.pause(atTime?: number): this    // Pause; optional snap-to time
tl.reverse(from?: number): this    // Play backward; optional start time
tl.restart(): this                 // Seek to 0, restore initial CSS, play forward
tl.seek(position: number): this    // Jump to time without playing

State — Getter / Setter

Call with no argument to read; call with argument to set and return this for chaining.

typescript
tl.duration(): number
tl.progress(value?: number): number | this   // Normalized 0–1
tl.time(value?: number): number | this       // Current time in seconds
tl.timeScale(value?: number): number | this  // Speed multiplier (2 = double speed)
tl.isActive(): boolean                       // True while playing
tl.getName(): string | undefined             // Registered name

Callbacks at Positions

Insert callback functions at specific points in the timeline:

typescript
tl.call(callback: (...args: unknown[]) => void, params?: unknown[], position?: string | number): this
typescript
Motion("sequence", [
  { target: ".step-1", from: { opacity: 0 }, duration: 0.5 },
  { target: ".step-2", from: { opacity: 0 }, duration: 0.5 },
])
  .call(() => console.log("halfway"), [], 0.5)
  .call(() => console.log("done"))
  .onPageLoad();

Lifecycle Callbacks

typescript
tl.onStart(cb: () => void): this
tl.onUpdate(cb: (progress: number, time: number) => void): this
tl.onComplete(cb: () => void): this
tl.onRepeat(cb: (count: number) => void): this

Cleanup

typescript
tl.kill(clearProps?: boolean): void  // clearProps=true (default) restores initial CSS
tl.clear(): this                     // Reset and rebuild without destroying the instance

Timeline Repeat

.withRepeat() loops the entire timeline — all steps replay as a group. This is different from per-animation repeat, which loops only one individual animation.

typescript
tl.withRepeat(config: number | RepeatConfig): this
typescript
// Infinite loop
Motion("pulse", ".dot", {
  from: { scale: 0.8, opacity: 0.6 },
  to:   { scale: 1.2, opacity: 1 },
  duration: 0.6,
}).withRepeat({ times: -1, yoyo: true }).onPageLoad();

// 3 times with yoyo + delay between cycles
Motion("bounce", [
  { target: ".box-a", to: { y: -40 }, duration: 0.4, ease: "power2.out" },
  { target: ".box-b", to: { y: -40 }, duration: 0.4, ease: "power2.out" },
]).withRepeat({ times: 3, yoyo: true, delay: 0.5 }).onPageLoad();

Appending Warning

Calling Motion(name, target, config) when name already exists in the registry appends new entries to that timeline — it does not replace it.

This is intentional for building multi-step timelines across multiple calls, but it will surprise you when trying to rebuild a timeline in response to a state change.

typescript
// ❌ Wrong — appends a second animation to the existing "hero" timeline
Motion("hero", "#hero", { from: { opacity: 0 }, duration: 0.8 }).onPageLoad();
// (somewhere later)
Motion("hero", "#hero", { from: { opacity: 0 }, duration: 0.8 }).onPageLoad();

// ✅ Correct — kill first, then rebuild from scratch
Motion("hero").kill();
Motion("hero", "#hero", { from: { opacity: 0 }, duration: 0.8 }).onPageLoad();

Static Methods

MethodSignatureDescription
Motion.set(target: TargetInput, vars: AnimationVars): voidZero-duration property apply. Processes through the full engine pipeline — values persist on DOM
Motion.get(name: string): Timeline | undefinedGet a timeline by name; undefined if not registered
Motion.has(name: string): booleanCheck whether a named timeline is registered
Motion.getNames(): string[]Array of all currently registered timeline names
Motion.kill(name: string): voidKill one timeline by name and restore initial CSS
Motion.killAll(): voidKill every registered timeline
Motion.reset(targets: TargetInput): voidKill animations, revert text splits, clear transform cache and inline styles
Motion.refreshScrollTriggers(): voidRecalculate all scroll trigger positions after layout changes
Motion.cleanup(): voidRemove [data-scrolltrigger-spacer] and [data-scrolltrigger-markers] DOM nodes
Motion.context(fn: () => void): MotionContextCreate a scoped context tracking all timelines created inside fn()
Motion.utilsMotionUtilsUtility functions — see below

Motion.set

Motion.set immediately applies CSS properties with no animation, no timeline. Use it to set initial element states before animations run, preventing flashes of unstyled content:

typescript
// Hide before page loads — no flash
Motion.set(".card", { opacity: 0, y: 30 });

// Reveal on scroll
Motion("cards", ".card", {
  from: { opacity: 0, y: 30 },
  duration: 0.6,
  stagger: 0.08,
}).onScroll({ scrub: false, toggleActions: "play none none none" });

Motion.context

Creates a scoped context that tracks all timelines created inside the callback. Returns a MotionContext object for clean teardown and reinitialisation — essential for SPA navigation, AJAX pagination, and React effects.

typescript
const ctx = Motion.context(() => {
  Motion("hero", ".title", { from: { opacity: 0, y: 30 }, duration: 0.8 }).onPageLoad();
  Motion("cards", ".card", { from: { y: 100 }, duration: 1 }).onScroll({ scrub: true });
});

// After dynamic content change (AJAX filter, pagination, etc.)
ctx.refresh();   // kills old animations, re-runs init, re-resolves selectors

// Add more animations to the context later
ctx.add(() => {
  Motion("footer", ".footer-item", { from: { opacity: 0 }, duration: 0.4 }).onPageLoad();
});

// Full cleanup (React unmount, route change, etc.)
ctx.revert();

React pattern:

typescript
useEffect(() => {
  const ctx = Motion.context(() => {
    Motion("fade", ".box", { from: { opacity: 0 }, duration: 0.6 }).onScroll({ scrub: true });
  });
  return () => ctx.revert();
}, []);

Astro View Transitions:

typescript
let ctx: MotionContext | undefined;

document.addEventListener("astro:page-load", () => {
  ctx?.revert();
  ctx = Motion.context(() => {
    Motion("page-reveal", ".animate-in", { from: { opacity: 0, y: 20 }, duration: 0.5 }).onPageLoad();
  });
});

Motion.utils

A collection of mathematical utilities. Also importable as a named export:

typescript
import { MotionUtils } from "@motion.page/sdk";
MethodSignatureDescription
toArray(target, scope?) → Element[]Convert selector / NodeList / HTMLCollection / Element to a flat array
clamp(min, max, value?) → number | fnClamp value within range. Curried when value is omitted
random(min, max, snap?) → numberRandom number with optional snap increment
snap(snapTo, value?) → number | fnSnap to nearest increment or array value. Curried when value is omitted
interpolate(start, end, progress) → numberLinear interpolation (lerp) between two values
mapRange(inMin, inMax, outMin, outMax, value?) → number | fnMap a value from one range to another. Curried when value is omitted
normalize(min, max, value?) → number | fnNormalize a value to 0–1. Curried when value is omitted
wrap(min, max, value?) → number | fnModular wrap within a range. Curried when value is omitted
typescript
// toArray — convert selector to elements for manual iteration
const panels = Motion.utils.toArray(".panel");

// clamp — keep a value within bounds
const clamped = Motion.utils.clamp(0, 100, 150); // → 100

// Curried form — create a reusable clamp function
const clamp0to1 = Motion.utils.clamp(0, 1);
clamp0to1(1.5); // → 1

// mapRange — convert scroll progress to rotation
const progress = Motion.utils.mapRange(0, window.innerHeight, 0, 360, scrollY);

// interpolate — lerp between colors or numbers
const value = Motion.utils.interpolate(0, 100, 0.75); // → 75

// wrap — loop an index within a range
const index = Motion.utils.wrap(0, slides.length, currentIndex + 1);

Trigger Methods

Timelines are inert until a trigger is chained. Creating a timeline with Motion(name, target, config) builds the animation but does not play or attach it.

MethodDescription
.play()Play immediately, right now
.onPageLoad(config?)Play when DOM is ready — fires immediately if already loaded
.onHover(config?)Play on mouseenter, react on mouseleave
.onClick(config?)Toggle on click
.onScroll(config?)Scrub or snap animation to scroll position
.onMouseMove(config?)Drive progress from mouse position
.onPageExit(config?)Intercept link clicks, play animation, then navigate
.onGesture(config)Map pointer / touch / wheel / scroll gestures to animation progress
.onCursor(config)Replace the native cursor with an animated custom cursor

Each trigger has its own configuration options — see the dedicated pages: Page Load · Hover · Click · Scroll Trigger · Mouse Movement · Page Exit · Gesture · Custom Cursor.

each: true — Independent Per-Element Instances

Without each, all matched elements share one timeline instance (group behaviour). With each: true, every element gets its own independent timeline.

typescript
// Group — hovering any card animates all of them
Motion("cards", ".card", { to: { y: -8 }, duration: 0.3 }).onHover({ onLeave: "reverse" });

// Independent — each card animates on its own hover
Motion("cards", ".card", { to: { y: -8 }, duration: 0.3 }).onHover({ each: true, onLeave: "reverse" });

each: true is supported in: .onHover(), .onClick(), .onScroll(), .onMouseMove(), .onGesture().


Stagger

stagger delays the start of each matched element’s animation, creating cascade effects.

Shorthand

Pass a number — each element waits that many seconds more than the previous:

typescript
Motion("list", ".item", {
  from: { opacity: 0, y: 20 },
  duration: 0.5,
  stagger: 0.08,
}).onPageLoad();
// Element 1 at 0s, element 2 at 0.08s, element 3 at 0.16s …

StaggerVars Object

OptionTypeDefaultDescription
eachnumberSeconds between each element
amountnumberTotal spread across all elements (alternative to each)
from'start' | 'end' | 'center' | 'edges' | 'random' | number'start'Which element starts first
grid'auto' | [number, number]Enable 2D grid staggering
axis'x' | 'y'Axis to measure distance for grid stagger
easestring'none'Easing for the stagger delay distribution
typescript
// Center-outward ripple on a grid
Motion("grid", ".cell", {
  from: { opacity: 0, scale: 0.6 },
  duration: 0.4,
  stagger: { each: 0.04, from: "center", grid: "auto" },
  ease: "power2.out",
}).onPageLoad();

See Stagger for the full stagger reference.


Repeat Config

The repeat option on individual animations. For repeating the whole timeline, use .withRepeat().

typescript
// Shorthand number
repeat: 3     // 3 additional cycles after the first play
repeat: -1    // Infinite

// Full config object
interface RepeatConfig {
  times:  number;    // Additional repetitions; -1 = infinite
  delay?: number;    // Seconds between each repetition
  yoyo?:  boolean;  // Alternate direction each cycle
}
typescript
// Infinite yoyo
Motion("breathe", ".logo", {
  to: { scale: 1.08 },
  duration: 1.2,
  ease: "sine.inOut",
  repeat: { times: -1, yoyo: true },
}).onPageLoad();

// 3 times with yoyo + rest delay
Motion("shake", ".error-field", {
  from: { x: -6 },
  to:   { x:  6 },
  duration: 0.06,
  ease: "none",
  repeat: { times: 7, yoyo: true, delay: 0 },
}).play();

Text Splitter

Split a text element into individual spans before animating. Each part becomes independently animatable.

split values

ValueEffect
'chars'Wrap each character in a <span>
'words'Wrap each word in a <span>
'lines'Wrap each line in a <span> (layout-dependent)
'chars,words'Both chars and words wrapped
'words,lines'Both words and lines wrapped
'chars,words,lines'All three levels wrapped
typescript
Motion("headline", "h1", {
  split: "words",
  from: { opacity: 0, y: "110%" },
  duration: 0.6,
  stagger: { each: 0.06, from: "start" },
  ease: "power3.out",
}).onPageLoad();

Split elements receive data attributes for CSS targeting:

AttributeSet onIndex attribute
[data-split-char]Each character spandata-char-index
[data-split-word]Each word spandata-word-index
[data-split-line]Each line spandata-line-index
[data-split]Root element

Inline elements (e.g. <span class="accent">) are preserved during splitting.

See Text Splitter for the full text splitter reference.


Mask

mask: true wraps each split element in an overflow: hidden container. The container clips the element during animation, creating a reveal effect where text appears to rise from beneath a surface.

Requires split to be set — mask has no effect without text splitting.

typescript
// Lines slide up from behind a clip — classic editorial reveal
Motion("reveal", ".tagline", {
  split: "lines",
  mask: true,
  from: { y: "100%" },
  duration: 0.7,
  stagger: 0.1,
  ease: "power3.out",
}).onPageLoad();

The mask wrapper receives [data-split-mask] for CSS targeting.


Flap (Text Flapper)

A split-flap display effect that cycles through intermediate characters before landing on the real text. Requires split: 'chars' on the same animation.

typescript
Motion("scramble", ".tagline", {
  split: "chars",
  flap: {
    type: "flip",
    charset: "alphanumeric",
    cycles: [2, 5],
  },
  from: { opacity: 0 },
  duration: 1.2,
  stagger: 0.04,
}).onPageLoad();

FlapConfig

OptionTypeDefaultDescription
typeFlapTypeRequired. Visual effect for each cycle
charsetCharsetPreset | string'alphanumeric'Characters to cycle through. Pass a string for a custom set
cyclesnumber | [number, number]3Intermediate characters before landing. Tuple = random range per cell
perspectivenumber400CSS perspective depth in px — applies to flip and board only
styledBoardbooleanfalseRender decorative flip-board cell UI — applies to board only
stableWidthboolean | 'cells' | 'container'true when styledBoardLayout-shift strategy. true/'cells' pins each char cell to the widest glyph; 'container' pins only the parent element’s outer width so letters flow naturally inside
preserveWhitespaceCellsbooleanfalseAnimate whitespace characters as cycling placeholder cells

FlapType values

ValueDescription
'flip'3D rotateX card flip. Composable with from/to transforms
'fade'Opacity crossfade. Owns opacity — do not set opacity in from/to
'slide'Characters slide in/out vertically
'blur'Blur dissolve. Owns filter — do not set filter in from/to
'scale'Characters scale in/out
'board'Decorative split-flap board cells — use with styledBoard: true
'none'No built-in style — run cycles only, drive visuals entirely via CSS

CharsetPreset values

ValueCharacters
'alphanumeric'A–Z, 0–9
'alpha'A–Z only
'numeric'0–9 only
'symbols'Punctuation and special characters
'binary'0 and 1 only
'hex'0–9, A–F
'katakana'Japanese katakana characters
'braille'Braille dot patterns

See Text Flapper for the full reference.


Fit (FLIP Morphing)

Measure both elements’ bounding rects and animate the visual delta between them — a FLIP-style morph. When fit is set, from and to are ignored.

typescript
Motion("morph", ".source", {
  fit: { target: ".destination", resize: true },
  duration: 0.5,
  ease: "power2.inOut",
}).play();

FitConfig

OptionTypeDefaultDescription
targetstringRequired. CSS selector for the destination element
absolutebooleanfalseConvert source to absolute positioning during animation
scalebooleantrueUse scaleX/scaleY — GPU-accelerated but distorts text and borders
resizebooleanfalseAnimate actual width/height — no distortion, triggers layout reflow. Mutually exclusive with scale

See Fit Animation for the full reference.


drawSVG

Animate the visible portion of an SVG stroke. The element must have a stroke — the SDK auto-computes stroke-dasharray if not set.

InputMeaning
"0% 100%"Full stroke visible
"0% 0%"Fully hidden (start state for draw-in)
"20% 80%"Middle portion only
"50%"Shorthand for "0% 50%"
"100px 500px"Pixel range along the stroke length
"true"Full stroke (keyword)
"false" / "none"Hidden
{ start: 20, end: 80 }Object — values are percentages 0–100 (not 0–1)
typescript
// Draw a path from left to right
Motion("draw", "path.line", {
  from: { drawSVG: "0% 0%" },
  to:   { drawSVG: "0% 100%" },
  duration: 1.2,
  ease: "power2.inOut",
}).onScroll({ scrub: true });

Object form: { start: 20, end: 80 } means 20% to 80% — values are percentages 0–100, not fractions 0–1.

See SVG Animations for the full drawSVG reference.


clip-path

Animate the CSS clip-path property between matching shape functions. Both clipPath (camelCase) and 'clip-path' (kebab-case, quoted) keys are accepted. The renderer writes both clip-path and -webkit-clip-path automatically.

ShapeExample
circle'circle(50% at 50% 50%)'
ellipse'ellipse(60% 30% at 50% 50%)'
inset'inset(10px 20px round 8px)'
polygon'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)'
rect'rect(10px 90px 90px 10px)'
xywh'xywh(10% 10% 80% 80%)'

Interpolation rules:

  • Same shape on both ends for smooth interpolation. Cross-shape (e.g. circle → inset) hard-swaps at progress ≥ 0.5.
  • polygon vertex counts must match between from and to for smooth morphing.
  • Units are preserved (%, px).
typescript
// Circular reveal — expand from center
Motion("reveal", "#hero", {
  from: { 'clip-path': 'circle(0% at 50% 50%)' },
  to:   { 'clip-path': 'circle(75% at 50% 50%)' },
  duration: 1,
  ease: "power2.out",
}).onPageLoad();

// Diagonal wipe on hover
Motion("wipe", ".card-image", {
  from: { clipPath: 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)' },
  to:   { clipPath: 'polygon(0% 0%, 0% 0%, 100% 100%, 0% 100%)' },
  duration: 0.5,
  ease: "power2.inOut",
}).onHover({ onLeave: "reverse" });

See Clip Path for the full reference.


Easing Reference

The ease string follows the format "family.direction" — lowercase, e.g. "power2.out". Unknown strings fall back to "power1.out". Easing names are case-insensitive.

FamilyVariantsBest for
power1.in · .out · .inOutSubtle UI motion, understated transitions
power2.in · .out · .inOutScroll reveals, card entrances — the everyday workhorse
power3.in · .out · .inOutHero sections, modals, bold entrances
power4.in · .out · .inOutCinematic moments, high-impact exits
sine.in · .out · .inOutDelicate fades, floating loops
expo.in · .out · .inOutDrawers, command palettes, snap-open UI
circ.in · .out · .inOutSorting animations, mechanical / precision UI
back.in · .out · .inOutPlayful cards, buttons — mild overshoot
elastic.in · .out · .inOutBadges, tooltips, success states — spring energy
bounce.in · .out · .inOutGame UI, mascots — discrete ball-drop bounces
roughGlitch effects, error shakes, organic feel
slow.in · .out · .inOutDramatic pauses, attention-drawing loops
noneScrub animations, progress bars — constant speed

Omitting ease uses the SDK default: equivalent to "power1.inOut".

See Easing Functions for visual comparison and usage guidance.


Callbacks

Lifecycle callbacks can be defined inline in AnimationConfig or chained on the Timeline:

typescript
// Inline in config
Motion("reveal", ".box", {
  from: { opacity: 0, y: 30 },
  duration: 0.6,
  onStart:           () => console.log("started"),
  onUpdate:          (progress) => console.log(`progress: ${progress.toFixed(2)}`),
  onComplete:        () => console.log("done"),
  onRepeat:          (count) => console.log(`repeat #${count}`),
  onReverseComplete: () => console.log("reversed"),
}).onPageLoad();

// Chained on the timeline
Motion("counter", "#count", {
  from: { opacity: 0 },
  duration: 0.4,
})
  .onStart(() => startTracking())
  .onUpdate((progress, time) => updateProgress(progress))
  .onComplete(() => finishTracking())
  .play();
CallbackSignatureFires when
onStart() => voidAnimation begins playing
onUpdate(progress: number, time: number) => voidEvery animation frame
onComplete() => voidAnimation reaches the end
onRepeat(count: number) => voidAfter each repeat cycle
onReverseComplete() => voidAnimation completes while playing in reverse

Dynamic Content & SPA

Use Motion.context() to scope animations for clean teardown and reinitialisation when DOM content changes — AJAX filters, pagination, SPA navigation, infinite scroll.

typescript
// Create a context — all timelines created inside are tracked
const ctx = Motion.context(() => {
  Motion("hero", ".title", { from: { opacity: 0, y: 30 }, duration: 0.8 }).onPageLoad();
  Motion("cards", ".card", { from: { y: 100 }, duration: 1 }).onScroll({ scrub: true });
});

// After dynamic content change (filter, pagination, etc.)
ctx.refresh();  // kills old animations, re-runs init, re-resolves all selectors

// Add more animations to the same context
ctx.add(() => {
  Motion("new-section", ".new-item", { from: { opacity: 0 }, duration: 0.4 }).onPageLoad();
});

// Full cleanup — React unmount, route change, etc.
ctx.revert();

WordPress plugin wraps animation code in Motion.context() automatically. Call MOTIONPAGE_FRONT.reinit() after AJAX content changes.

See SPA Integration for framework-specific patterns (React, Vue, Next.js, Astro).


Anti-Patterns

DON’TDO instead
from: { opacity: 0, y: 50 }, to: { opacity: 1, y: 0 }from: { opacity: 0, y: 50 }opacity:1 and y:0 are natural defaults
import { Motion } from '@motion/sdk'import { Motion } from '@motion.page/sdk'
Call Motion("name", ...) twice expecting replacementCall Motion("name").kill() first, then rebuild
Target multiple independent elements without each: trueAdd each: true to the trigger config
querySelectorAll(".card").forEach(el => Motion(..., el, ...))Motion("cards", ".card", {...}).onHover({ each: true })
Mix external animation libraries with Motion SDKUse Motion SDK exclusively
drawSVG: { start: 0.2, end: 0.8 } expecting fractionsObject values are percentages 0–100: { start: 20, end: 80 }
Use playNext/playPrevious without each: trueAlways combine playNext/playPrevious actions with each: true
Hover/HoverEnd gesture events on windowAlways specify a target element for hover events
Set flap.type: 'fade' and also animate opacityfade type owns opacity — drop opacity from from/to

TypeScript Types

All types are importable from @motion.page/sdk:

typescript
import type {
  // Core
  AnimationVars,
  AnimationConfig,
  AnimationEntry,
  TargetInput,
  ObjectTarget,
  AnimationTarget,
  EasingFunction,

  // Config options
  StaggerVars,
  RepeatConfig,
  SplitType,
  PathConfig,
  FitConfig,
  FlapConfig,

  // Trigger configs
  HoverConfig,
  ClickConfig,
  ScrollConfig,
  MouseMoveConfig,
  MarkerConfig,
  PageLoadConfig,
  PageExitConfig,

  // Gesture
  GestureConfig,
  GestureEvent,
  GestureAction,
  GestureInputType,
  TimelineAction,

  // Cursor
  CursorConfig,
  CursorStateVars,
  CursorSqueezeConfig,

  // Context
  MotionContext,
} from "@motion.page/sdk";

// Namespace import — all types under Types.*
import { Types } from "@motion.page/sdk";

Browser Support

Chrome 90+ · Firefox 90+ · Safari 15+ · Edge 90+


Related: Core Concepts · Getting Started · Sequencing · Stagger · Easing · SPA Integration