Animation utilities for terminal interfaces.
@termuijs/motion provides spring-based animations for natural physical movement and easing-based transitions for time-driven animations. It is designed specifically for terminal UIs where smooth updates and low CPU usage matter.
All animations automatically respect reduced-motion environments. When NO_MOTION=1 is set, animations instantly resolve to their final value without running animation loops. This keeps applications accessible and CI-friendly without requiring extra logic in your code.
npm install @termuijs/motionRequires @termuijs/core.
Spring animations simulate physical motion using stiffness, damping, and mass. Instead of manually controlling animation timing, the spring calculates realistic movement automatically.
import { animateSpring } from '@termuijs/motion'
animateSpring(
{ from: 0, to: 100 },
(value) => progressBar.setValue(value / 100),
() => console.log('done'),
)Preset configurations provide common motion styles without manually tuning physics values.
import { SPRING_PRESETS, animateSpring } from '@termuijs/motion'
animateSpring(
{
from: 0,
to: 1,
...SPRING_PRESETS.stiff,
},
onFrame,
)| Preset | Description |
|---|---|
default |
Balanced motion suitable for most UI interactions |
stiff |
Fast and responsive with minimal bounce |
gentle |
Smooth and relaxed motion |
wobbly |
Playful motion with noticeable bounce |
slow |
Slower movement for dramatic transitions |
molasses |
Extremely slow motion with heavy easing |
| Option | Type | Default | Description |
|---|---|---|---|
stiffness |
number |
170 |
Controls spring tension. Higher values create snappier motion |
damping |
number |
26 |
Controls how quickly oscillation settles. Higher values reduce bounce |
mass |
number |
1 |
Controls inertia. Higher values create heavier movement |
Transitions are useful for animations that should complete within a fixed duration rather than following physical behavior.
import { transition } from '@termuijs/motion'
transition({
from: 0,
to: 1,
duration: 300,
easing: 'ease-out',
onFrame: (v) => widget.setOpacity(v),
})Easing curves define how values accelerate and decelerate during a transition.
| Easing | Description |
|---|---|
linear |
Constant animation speed |
ease-in |
Starts slowly and accelerates |
ease-out |
Starts quickly and slows near the end |
ease-in-out |
Smooth acceleration and deceleration |
| Option | Type | Description |
|---|---|---|
from |
number |
Starting value |
to |
number |
Final value |
duration |
number |
Duration in milliseconds |
easing |
string |
Easing curve used during interpolation |
onFrame |
(value: number) => void |
Called on every animation frame |
onComplete |
() => void |
Called after the transition finishes |
When NO_MOTION=1 is enabled, both animateSpring and transition skip animation frames and immediately resolve to their final values.
NO_MOTION=1 node app.jsThis behavior is automatic and does not require additional checks in application code.
@termuijs/motion is optimized for terminal rendering performance.
Animations internally use timerPoolSubscribe from @termuijs/core instead of creating independent timers. Multiple animations share a single update loop, helping CPU usage remain stable even when many animations run simultaneously.
For best performance:
| Recommendation | Reason |
|---|---|
| Reuse animations when possible | Reduces unnecessary allocations |
| Avoid excessive simultaneous animations | Prevents unnecessary terminal redraws |
| Prefer springs for interactive motion | Produces smoother and more natural updates |
| Keep durations reasonable | Improves responsiveness in terminal environments |
All active animations share a centralized 16ms timer managed by @termuijs/core.
This avoids creating multiple setTimeout or setInterval loops and keeps rendering performance predictable across large terminal applications.
@termuijs/motion provides utilities for creating smooth and expressive animations in terminal applications. It includes spring-based animations for natural physical movement, transition-based animations for fixed-duration effects, stagger utilities for coordinating multiple animations, and sequencing helpers for composing complex animation flows.
The library is designed specifically for terminal environments where efficient rendering and predictable performance are important. It offers reusable animation primitives that can be combined to build responsive and visually appealing interfaces.
@termuijs/motion also respects reduced-motion environments automatically. When the NO_MOTION=1 environment variable is set, animations immediately resolve to their final value instead of running animation loops. This behavior improves accessibility and makes automated testing and CI environments deterministic without requiring additional application logic.
Spring animations simulate natural physical movement by using configurable physics parameters such as stiffness, damping, and mass. They are ideal for interactive UI elements that should feel responsive rather than moving at a fixed speed.
animateSpring continuously updates a value until the spring reaches equilibrium, invoking a callback on every animation frame.
import { animateSpring } from '@termuijs/motion'
animateSpring(
0,
100,
{},
(value) => progressBar.setValue(value / 100),
() => console.log('done'),
)stepSpring can be used when you want to manually advance the spring simulation one step at a time, giving you fine-grained control over the animation loop.
import { stepSpring, SPRING_PRESETS } from '@termuijs/motion'
let state = {
value: 0,
velocity: 0,
target: 100,
done: false,
}
state = stepSpring(
state,
SPRING_PRESETS.default,
0.016,
)
console.log(state.value)SPRING_PRESETS provides predefined configurations for common animation styles, allowing consistent motion without manually tuning spring parameters.
import { animateSpring, SPRING_PRESETS } from '@termuijs/motion'
animateSpring(
0,
1,
SPRING_PRESETS.stiff,
onFrame,
)Choose a preset that matches the desired interaction style, such as quick and responsive motion or slower, more expressive animations.
Transitions are useful when an animation should complete within a fixed duration instead of following spring physics. The library provides a generic transition utility along with several pre-built helpers for common effects.
transition animates a value from 0 to 1 over a specified duration and calls onFrame on every update.
import { transition, easings } from '@termuijs/motion'
transition({
durationMs: 300,
easing: easings.easeOut,
onFrame: (progress) => {
widget.setOpacity(progress)
},
onComplete: () => {
console.log('Transition complete')
},
})Common animation effects are available as reusable helpers:
fadeIn– gradually increases opacity.fadeOut– gradually decreases opacity.slideIn– animates an element from an offset position.typewriter– reveals text character by character.pulse– continuously oscillates an intensity value.
Example:
import {
fadeIn,
fadeOut,
slideIn,
typewriter,
} from '@termuijs/motion'
fadeIn(250, (opacity) => {
widget.setOpacity(opacity)
})
slideIn(20, 300, (offset) => {
widget.setOffset(offset)
})
typewriter("Hello, TermUI!", 500, (visibleChars) => {
widget.setText(
"Hello, TermUI!".slice(0, visibleChars)
)
})The easings object provides reusable easing functions for controlling animation speed and acceleration. Depending on the desired effect, animations can use linear, ease-in, ease-out, ease-in-out, and cubic easing functions.
import { transition, easings } from '@termuijs/motion'
transition({
durationMs: 400,
easing: easings.easeInOutCubic,
onFrame: (progress) => {
widget.setProgress(progress)
},
})When multiple animations should not start simultaneously, stagger can be used to introduce a fixed delay between their start times. The first animation begins immediately, the second starts after the specified delay, the third after twice the delay, and so on.
This is useful for creating cascading effects such as animated lists, menus, or sequential UI elements.
import { stagger } from '@termuijs/motion'
const cancel = stagger(
[
runner1,
runner2,
runner3,
],
100,
() => {
console.log('All staggered animations completed')
},
)The function returns a cancel function that can be called to stop any pending or active animations.
Complex animation flows can be composed using sequence and parallel.
sequenceexecutes animations one after another.parallelexecutes multiple animations simultaneously and invokes the completion callback when all have finished.
Both utilities operate on AnimationRunner functions, allowing animation logic to be composed and reused.
import { sequence } from '@termuijs/motion'
sequence(
[
runner1,
runner2,
runner3,
],
() => {
console.log('Sequence complete')
},
)import { parallel } from '@termuijs/motion'
parallel(
[
runner1,
runner2,
runner3,
],
() => {
console.log('Parallel animations complete')
},
)Using these helpers makes it easy to build complex animation pipelines while keeping animation logic modular and readable.
Animations can be tested deterministically using the virtual clock utilities provided by the testing ecosystem. Instead of waiting for real time to pass, tests can advance time synchronously, making animation behavior predictable and CI-friendly.
import type { VirtualClock } from '@termuijs/motion'When reduced-motion mode is enabled through the NO_MOTION environment variable, animations immediately resolve to their final state rather than running animation loops. This behavior simplifies automated testing while maintaining accessibility for users who prefer reduced motion.
For application tests, the virtual clock available through @termuijs/testing can be used to advance animation time synchronously without relying on real timers.
Additional documentation is available at:
https://www.termui.io/docs/motion/springs
MIT