Motion
All motion APIs work in both modes: in DOM mode the same numbers become CSS
transforms, and in GPU mode they only update a matrix. Durations are in
milliseconds. With prefers-reduced-motion, motion jumps to its end state.
Transitions
Section titled “Transitions”const gpu = useGPUHost();const handle = gpu.transition("card", { keyframes: [{ offset: 0 }, { offset: 1, x: 120, rotation: 10, opacity: 0.8 }], timing: { duration: 500, easing: "ease-out" },});const result = await handle.finished; // { status: "completed" | "cancelled" | ..., reason }- Keyframes are numeric:
x,y,rotation(degrees),scaleX,scaleYandopacity. An omitted channel means its identity value, not “keep the previous one”. - Layers can also tilt out of the screen:
tiltXandtiltYin degrees (at most ±60, as CSSrotateXandrotateY), viewed fromperspectiveCSS px (1600 by default). Clicks follow the tilted controls exactly. A tilted layer must not sit inside a scrolling box that clips it, or clip its own overflow. - Starting a new transition on the same layer continues smoothly from where the current one is.
- Rotation and scale pivot around the layer’s
origin. Setorigin="center"on theSurface(or passorigintouseSurfaceRef) for natural spins. - While a transition runs, the layer’s native controls stay clickable at their moving positions.
Groups and stagger
Section titled “Groups and stagger”gpu.transitionGroup(cards.map(card => ({ id: card.id, motion: dealTo(card) })), 90);All items start together, offset by the stagger (0–1000 ms), and each has its own handle. Invalid input fails as a whole, before anything moves.
Enter and exit with Presence
Section titled “Enter and exit with Presence”<Surface id="toast" layout={...}> <Presence id="toast" present={open} enter={slideIn} exit={slideOut} onComplete={result => log(result)}> <Toast onDismiss={() => setOpen(false)} /> </Presence></Surface>Presence keeps its children mounted through the exit motion, then removes them.
The Surface stays as a stable layer. The toast’s buttons are the real React
buttons the whole time.
Direct motion
Section titled “Direct motion”controller.animate(id, target, { duration })interpolates toward a target transform on the GPU. It is meant for interactive, imperative motion.controller.setTransform(id, transform)sets a transform immediately. It is safe to call every frame (drag, springs, physics), including whileenable()is admitting; admission presents the latest value.- A
Surface’stransformprop is the resting transform. React updates to it during a transition become the end state when that transition finishes.
Animation ownership (existing CSS or WAAPI animations)
Section titled “Animation ownership (existing CSS or WAAPI animations)”If a layer already has a CSS keyframe animation or a Web Animation of transform or
opacity, GPU mode needs to own it, because the GPU draws the layer’s transform
itself.
controller.createMotion(id, descriptor)creates a managed animation from numeric keyframes. Call.own()to hand it to the GPU.play,pauseandseekwork either way.controller.ownAnimation(id, animation)takes over an existing Web Animation;ownCSSAnimation(id, { rule, keyframes })takes over a CSS animation whose rules you point to.playback.release()hands the source back and returns the surface to DOM.playback.release({ freeze: true })releases only that layer and keeps GPU on: the layer stays at its last pose (WAAPI andcreateMotionsources).
Ownership works while GPU is already on. For new code, prefer transition and
Presence; ownership exists for animations you cannot rewrite.
Hidden tabs
Section titled “Hidden tabs”Transitions pause while the document is hidden and resume with the same progress. They never jump to the end.