npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@termuijs/motion

v0.1.6

Published

Spring physics and easing animations for TermUI

Readme

@termuijs/motion

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.


Install

npm install @termuijs/motion

Requires @termuijs/core.


Springs

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'),
)

Spring Presets

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 |


Spring Options

| 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

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

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 |


Transition Options

| 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 |


NO_MOTION Support

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.js

This behavior is automatic and does not require additional checks in application code.


Performance Notes

@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 |


Timer Pool Integration

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.


Animation Guide

Overview

@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.


Springs

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.

Using animateSpring

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'),
)

Using stepSpring

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)

Using SPRING_PRESETS

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 and easings

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.

Using transition

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')
    },
})

Pre-built transition helpers

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)
    )
})

Easings

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)
    },
})

Stagger

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.

Sequencing

Complex animation flows can be composed using sequence and parallel.

  • sequence executes animations one after another.
  • parallel executes 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.

Running animations sequentially

import { sequence } from '@termuijs/motion'

sequence(
    [
        runner1,
        runner2,
        runner3,
    ],
    () => {
        console.log('Sequence complete')
    },
)

Running animations in parallel

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.

Testing motion

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.


Documentation

Additional documentation is available at:

https://www.termui.io/docs/motion/springs


License

MIT