Animation
Rezi supports declarative motion in two layers:
Rezi supports declarative motion in two layers:
- Hook-driven numeric animation for custom widget behavior (
useTransition,useSpring,useSequence,useStagger,useAnimatedValue,useParallel,useChain). - Container transitions for layout/surface animation on
ui.box(...),ui.row(...),ui.column(...), andui.grid(...)viatransition/exitTransitionprops.
Hook animations share a frame driver inside the widget runtime, so concurrent animations advance together instead of each hook spinning its own timer loop.
Choose the right API
- Use
useTransition(...)for direct time-based interpolation between numeric targets. - Use
useSpring(...)for natural motion driven by spring physics. - Use
useSequence(...)for keyframe timelines. - Use
useStagger(...)for list/rail entry timing offsets. - Use
useAnimatedValue(...)when you needvalue + velocity + isAnimatingmetadata. - Use
useParallel(...)for multiple transitions that run together. - Use
useChain(...)for step-by-step transitions. - Use container
transitionprops when you want runtime-managed position/size/opacity transitions without writing hook logic.
Easing Families and Usage
- Linear: constant-rate movement for utility transitions.
- Quad: subtle, low-energy transitions for small UI nudges.
- Cubic: standard default-feeling motion for most interactions.
Easing Presets Reference
| Preset | Family | Typical use |
|---|---|---|
linear | linear | Utility animation, deterministic rate |
easeInQuad | quad | Gentle acceleration in |
easeOutQuad | quad | Gentle deceleration out |
easeInOutQuad | quad | Soft in/out transitions |
easeInCubic | cubic | Stronger acceleration |
easeOutCubic | cubic | Standard UI deceleration |
easeInOutCubic | cubic | Balanced default for panels/opacity |
easeInExpo | expo | Fast late acceleration |
easeOutExpo | expo | Fast early arrival |
easeInOutExpo | expo | Dramatic in/out motion |
easeInBack | back | Anticipation before moving forward |
easeOutBack | back | Overshoot on settle |
easeInOutBack | back | Anticipation + overshoot |
easeOutBounce | bounce | Playful settling |
easeInBounce | bounce | Reverse bounce-in |
Hook Example
import {
defineWidget,
ui,
useAnimatedValue,
useSequence,
useStagger,
} from "@rezi-ui/core";
type ReactorProps = {
target: number;
modules: readonly string[];
key?: string;
};
export const ReactorRow = defineWidget<ReactorProps>((props, ctx) => {
const energy = useAnimatedValue(ctx, props.target, {
mode: "spring",
spring: { stiffness: 190, damping: 22, onComplete: () => {} },
});
const pulse = useSequence(ctx, [0.25, 1, 0.4, 0.9], {
duration: 120,
loop: true,
playback: { rate: 1 },
onComplete: () => {},
});
const stagger = useStagger(ctx, props.modules, {
delay: 40,
duration: 180,
easing: "easeOutCubic",
onComplete: () => {},
});
return ui.column({ gap: 1 }, [
ui.text(
`value=${energy.value.toFixed(2)} vel=${energy.velocity.toFixed(2)} anim=${energy.isAnimating}`,
),
ui.row(
{ gap: 1 },
props.modules.map((label, i) =>
ui.box(
{
key: label,
border: "rounded",
p: 1,
opacity: (0.3 + 0.7 * (stagger[i] ?? 0)) * pulse,
},
[ui.text(label)],
),
),
),
]);
});Retargeting and Completion Semantics
- Retargeting mid-flight starts from the current interpolated value (no jump).
- Looping timelines (
useSequence(..., { loop: true })) continue indefinitely. onCompleteis supported onuseTransition,useSpring,useSequence,useStagger,useAnimatedValue,useParallel, anduseChain.useAnimatedValue(..., { mode: "transition" })honors transition playback controls (paused,reversed,rate) and resumes from the retained transition progress.useStaggertracks item identity, not just item count, so same-length replacements restart the stagger.
Container Transitions
Container widgets support declarative render-time transitions:
ui.box({ transition })ui.row({ transition })ui.column({ transition })ui.grid({ transition })
ui.row(
{
key: "status-row",
gap: 1,
transition: {
duration: 220,
easing: "easeInOutCubic",
properties: ["position", "size"],
},
},
[ui.text("Animated row")],
);Behavior:
propertiesdefaults to"all"when omitted (position,size, andopacity).properties: []disables all transition tracks.opacityis clamped to[0..1].- Opacity animation currently affects
boxsurfaces;row/column/gridopacity transitions are no-op and resolve to opacity1.
Exit Animations
Use exitTransition to animate a container out before unmount cleanup:
ui.box(
{
key: "toast",
border: "rounded",
p: 1,
transition: { duration: 160, easing: "easeOutCubic" },
exitTransition: { duration: 200, easing: "easeInCubic", properties: ["opacity"] },
},
[ui.text("Saved")],
);Lifecycle:
- On removal, the runtime keeps the keyed subtree in an exit track.
- The exit node is rendered during the transition window.
- Exit nodes do not participate in focus traversal or hit-testing metadata.
- When exit duration completes, deferred local-state cleanup runs and the subtree is removed.
- If the same keyed node reappears before completion, the exit animation is canceled.
Playback Control
useTransition and useSequence support:
playback: {
paused?: boolean;
reversed?: boolean;
rate?: number; // default 1
}paused: truefreezes at the current sampled value.reversed: trueruns timeline time backward.ratescales elapsed time (0.5half-speed,2double-speed).
Color Interpolation
Use animation utilities for RGB interpolation:
import { interpolateRgb, interpolateRgbArray, rgb } from "@rezi-ui/core";
const mid = interpolateRgb(rgb(0, 0, 0), rgb(255, 255, 255), 0.5);
const ramp = interpolateRgbArray(rgb(0, 40, 80), rgb(220, 200, 40), 8);- Channels are linearly interpolated in RGB space.
- Output channels are clamped to integer
[0..255].
Orchestration
Use orchestration helpers for grouped transitions:
useParallel(ctx, animations)runs multiple transitions concurrently and returns per-entry{ value, isAnimating }.useChain(ctx, steps)runs sequential transitions and returns{ value, currentStep, isComplete }.
useAnimatedValue
useAnimatedValue composes transition/spring strategies behind one API:
const animated = useAnimatedValue(ctx, target, {
mode: "transition",
transition: { duration: 180, easing: "easeOutCubic" },
});
animated.value;
animated.velocity;
animated.isAnimating;Defaults and Safety
- Hook durations are normalized to safe non-negative integer milliseconds.
- Hook delay defaults:
useTransition:0msuseSpring:0ms
useTransitiondefault duration:160ms.useSequencedefault per-segment duration:160ms.useStaggerdefaults:delay=40ms,duration=180ms.- Container transition default duration:
180ms. - Non-finite numeric targets snap safely instead of producing unstable interpolation.
Performance Guidance
- Drive animation from state targets; avoid ad-hoc timer loops in app/view code.
- Keep animated values numeric and local; derive display strings lazily in render.
- For large animated collections, combine
useStagger(...)with windowing (ui.virtualList(...)) to cap visible work.
Related
Composition
Rezi supports reusable, stateful widgets via defineWidget. Composite widgets integrate with the runtime's reconciliation and update pipeline while keeping your view pure.
Runtime & Layout
Rezi's runtime turns state into frames through a deterministic pipeline. This page is an index for the runtime internals and the layout model used across widgets.