@leonardosalasd/webmotion
v0.1.0
Published
Beautiful, lightweight, editable animation components for React, so every element can enter, move, and leave with intention.
Downloads
105
Maintainers
Readme
WebMotion
Beautiful, lightweight, editable animation components for React.
Native apps move with intention. You always see where things come from and where they go. The web usually just pops them into place. WebMotion brings that sense of motion to your app with a handful of components you can drop in and forget about.
Why
Most things on a page appear out of nowhere. A card, a modal, a list, all there one frame and gone the next. WebMotion gives every element a way to enter, move, and leave so the interface feels like it has weight and direction, the way good mobile apps do, without you hand-writing keyframes every time.
Highlights
- Tiny and dependency-free. The whole library is a few KB gzipped and ships zero runtime dependencies beyond React.
- Hybrid engine. By default it rides the browser's Web Animations API, even baking springs
into a
linear()easing so there's no per-frame JavaScript. When you need a real, interruptible spring (gestures, live values), reach for theuseSpringhook. - Drop-in components.
FadeIn,SlideIn,ScaleIn,BlurIn,Reveal,Stagger, plus aMotionprimitive when you want full control. - Editable by design. Every variant, easing, and spring preset is exported. Read them, tweak them, or register your own.
- Accessible by default. It respects
prefers-reduced-motionand renders the final state instead of animating when someone asks for less motion. - Typed end to end. Written in TypeScript, with autocomplete for every prop and preset.
Install
pnpm add @leonardosalasd/webmotionnpm install @leonardosalasd/webmotionyarn add @leonardosalasd/webmotionReact 17, 18, and 19 are all supported (it's a peer dependency).
Quick start
import { FadeIn, SlideIn, Stagger } from '@leonardosalasd/webmotion';
export function Hero() {
return (
<section>
<SlideIn direction="up">
<h1>Everything arrives with intention</h1>
</SlideIn>
<FadeIn delay={150}>
<p>No more elements blinking into existence.</p>
</FadeIn>
<Stagger animation="fade-up" interval={80}>
<Card />
<Card />
<Card />
</Stagger>
</section>
);
}That's it. Each component animates itself in on mount, or as it scrolls into view, and gets out of your way otherwise.
Playground
Want to see everything move before you install anything? Clone the repo and run the local playground:
pnpm install
pnpm -C playground install
pnpm -C playground devIt opens a page that shows every component and preset, with a button to replay the animations. The playground imports the library straight from source, so any edit you make shows up live.
Components
Motion
The primitive the others are built on. Pass a named animation or your own from/to states.
<Motion animation="zoom" spring="bouncy">
<Badge>New</Badge>
</Motion>
// or fully custom
<Motion
from={{ opacity: 0, transform: 'translateY(40px) rotate(-4deg)' }}
to={{ opacity: 1, transform: 'translateY(0) rotate(0deg)' }}
duration={600}
>
<Panel />
</Motion>FadeIn, SlideIn, ScaleIn, BlurIn
Ergonomic shortcuts for the entrances you reach for most.
<FadeIn>Soft and simple</FadeIn>
<SlideIn direction="left" distance={32}>From the side</SlideIn>
<ScaleIn spring="snappy">Grows into place</ScaleIn>
<BlurIn blur={12}>Sharpens into focus</BlurIn>Reveal
Motion with the scroll trigger turned on. Anything inside animates as it enters the viewport.
<Reveal animation="fade-up" once amount={0.3}>
<Testimonial />
</Reveal>Stagger
Plays its children one after another. Give it an animation and it applies it to each child,
spacing out their delays so a list cascades instead of landing all at once.
<Stagger animation="fade-up" interval={60} trigger="inView">
{items.map((item) => (
<li key={item.id}>{item.label}</li>
))}
</Stagger>Shared props
These work on Motion and every component built on it:
| Prop | Type | Default | What it does |
| ------------------------ | ------------------------------ | ----------- | -------------------------------------------------- |
| animation | VariantName | "fade" | Which built-in entrance to use. |
| from / to | MotionKeyframe | — | Roll your own states instead of a variant. |
| duration | number (ms) | 500 | How long the animation runs. |
| delay | number (ms) | 0 | Wait before it starts. |
| easing | Easing | "easeOut" | A preset name, a raw CSS easing, or { spring }. |
| spring | SpringPreset \| SpringConfig | — | Use a spring instead of an easing curve. |
| trigger | "mount" \| "inView" | "mount" | Animate on mount or when scrolled into view. |
| once | boolean | true | For in-view triggers, only animate the first time. |
| amount | number (0–1) | — | How much must be visible to count as in view. |
| as | ElementType | "div" | Render as a different element. |
| disabled | boolean | false | Skip the animation and show the final state. |
| onStart / onComplete | () => void | — | Lifecycle callbacks. |
Built-in variants
fade, fade-up, fade-down, fade-left, fade-right, scale, scale-up, zoom, pop,
blur, rotate, flip-x, flip-y.
import { variants, getVariant } from '@leonardosalasd/webmotion';
// peek at what a variant resolves to
const { from, to } = getVariant('fade-up', { distance: 48 });Easings and springs
import { easings, springs } from '@leonardosalasd/webmotion';- Easing presets:
linear,ease,easeIn,easeOut,easeInOut,emphasized,anticipate. - Spring presets:
gentle,smooth,snappy,bouncy,wobbly,stiff.
Springs are baked into a CSS linear() easing so the browser plays them with no JavaScript
running each frame. You can bake one yourself:
import { springToLinearEasing } from '@leonardosalasd/webmotion';
const { easing, duration } = springToLinearEasing({ stiffness: 260, damping: 14 });Hooks
For the cases the components don't cover.
useMotion
The engine behind every component. Give it two states and timing, get back a ref and the initial style.
import { useMotion } from '@leonardosalasd/webmotion';
function Toast() {
const { ref, style } = useMotion({
from: { opacity: 0, transform: 'translateY(12px)' },
to: { opacity: 1, transform: 'translateY(0)' },
spring: 'snappy',
});
return (
<div ref={ref} style={style}>
Saved
</div>
);
}useSpring
A real, interruptible spring that runs in JavaScript. The returned number chases its target, so change the target whenever you want and it redirects mid-flight. Great for gestures and values you map straight onto style.
import { useSpring } from '@leonardosalasd/webmotion';
function Follower({ x }: { x: number }) {
const sx = useSpring(x, 'wobbly');
return <div style={{ transform: `translateX(${sx}px)` }} />;
}useInView
Reports when an element scrolls into view, with once, amount, and margin options.
useReducedMotion
Tells you whether the visitor has asked for less motion, so you can branch your own UI.
Accessibility
WebMotion checks prefers-reduced-motion for you. When it's set to reduce, components skip the
animation and render their final state, so the content is still there, just calmer. You don't
have to do anything to get this behavior.
Server-side rendering
It's SSR-safe. Components render their starting state on the server and animate once they hydrate in the browser, so there's no flash of the final state.
Roadmap
- Exit animations and presence handling for elements leaving the tree
- Gesture helpers built on
useSpring - A live playground to preview and copy variants
- Adapters for other frameworks on top of the same core
Ideas and PRs for any of these are very welcome.
Contributing
Contributions are open to everyone. Check out CONTRIBUTING.md for how to set up the project, the scripts you'll use, and what makes a change easy to merge. First-timers are genuinely welcome.
Sponsor
If WebMotion saves you time, you can support its development through GitHub Sponsors. Every bit helps keep it moving.
License
MIT © Leonardo Salas
