kinety
v0.1.1
Published
Lightweight animation primitives for Remotion — FadeIn, Spin, Pop, and more.
Maintainers
Readme
Kinety
Lightweight animation primitives for Remotion — declarative, keyframe-based motion for videos and motion graphics.
import { FadeIn, Spin, Pop } from "kinety"
export const MyVideo = () => (
<>
<FadeIn from={0} duration={30}>
<h1>Welcome</h1>
</FadeIn>
<Spin from={30} duration={60} loop>
<Logo />
</Spin>
</>
)Why Kinety
Remotion gives you interpolate and spring, but wiring up multi-property, multi-stage animations (fade in, overshoot, settle) by hand means a lot of repeated boilerplate. Kinety wraps that pattern into:
- Keyframe objects instead of manual
interpolatecalls per property - Ready-made components (
<FadeIn>,<Spin>,<Pop>, ...) for common motion patterns - A
<Motion>escape hatch for fully custom keyframes without writing a new component - Zero dependencies beyond
reactandremotion(both peer deps — nothing is bundled)
Install
npm install kinety remotion reactremotion and react are peer dependencies — Kinety uses whatever versions are already in your project.
Quick start
Every component wraps its children in a <div> and animates opacity, scale, rotate, translateX, and translateY via CSS transform, driven by the current Remotion frame.
import { FadeIn } from "kinety"
<FadeIn from={10} duration={20}>
<p>Fades in starting at frame 10, over 20 frames.</p>
</FadeIn>| Prop | Type | Default | Description |
|-------------|------------|---------|-------------------------------------------------------------------------------|
| from | number | 0 | Frame the animation starts on |
| duration | number | 30 | Number of frames the animation runs over |
| loop | boolean | false | Repeats the animation every duration frames after from |
| keyframes | Keyframes| preset | Overrides the component's built-in keyframes entirely |
| style | CSSProperties | — | Extra styles merged onto the wrapper <div>, applied before the animation |
Built-in components
| Component | Effect |
|------------------|------------------------------------------------------|
| <FadeIn> | Opacity 0 → 1 |
| <FadeOut> | Opacity 1 → 0 |
| <Spin> | Rotate 0 → 360deg (pair with loop for continuous spin) |
| <ScaleIn> | Scale 0 → 1 with fade |
| <Pop> | Scale 0 → 1.15 → 1 — overshoot pop-in with fade |
| <SlideInLeft> | Translate in from the left with fade |
| <SlideInRight> | Translate in from the right with fade |
| <SlideInUp> | Translate in from below with fade |
| <SlideInDown> | Translate in from above with fade |
| <Bounce> | Vertical bounce, settles at rest |
| <Pulse> | Scale breathing effect (1 → 1.08 → 1), good with loop |
| <Motion> | No default keyframes — pass your own via keyframes |
Examples
Looping spinner:
<Spin from={0} duration={60} loop>
<Icon name="loader" />
</Spin>One-shot pop-in badge:
<Pop from={20} duration={20}>
<Badge>New</Badge>
</Pop>Custom keyframes with <Motion>:
<Motion
from={0}
duration={40}
keyframes={{
0: { translateX: -200, rotate: -20, opacity: 0 },
100: { translateX: 0, rotate: 0, opacity: 1 },
}}
>
<Card />
</Motion>Overriding a preset's keyframes on a named component:
<FadeIn
from={0}
duration={30}
keyframes={{ 0: { opacity: 0, scale: 0.8 }, 100: { opacity: 1, scale: 1 } }}
>
<Title />
</FadeIn>Writing custom keyframes
Keyframes are keyed by percentage of progress through the animation (0–100), not raw frame numbers — Kinety maps from/duration onto that range internally. Any subset of properties can be set per keyframe; properties don't need to appear at every step.
const keyframes = {
0: { scale: 0, opacity: 0.2 },
50: { scale: 1.5 },
100: { scale: 1, opacity: 1 },
}Here, opacity only has two defined points (0 and 100) and interpolates directly between them, ignoring the scale-only keyframe at 50.
Animatable properties:
type AnimationProperties = {
opacity: number
rotate: number // degrees
scale: number
translateX: number // px
translateY: number // px
}The useAnimation hook
For custom components that need animated styles without the <div> wrapper, use the hook directly:
import { useAnimation } from "kinety"
function MyComponent() {
const style = useAnimation(
{ 0: { opacity: 0 }, 100: { opacity: 1 } },
{ from: 0, duration: 30 }
)
return <img src="logo.png" style={style} />
}useAnimation(keyframes, options) returns { transform: string; opacity: number }, ready to spread onto any element's style.
The Animation class (core engine)
For advanced use — e.g. driving non-React consumers, or computing values outside a render — use Animation directly:
import { Animation } from "kinety"
const fadeIn = new Animation({
0: { scale: 0, opacity: 0.2 },
50: { scale: 1.5 },
100: { scale: 1, opacity: 1 },
})
const { transform, opacity } = fadeIn.animate({ frame, from: 90, duration: 85 })animate clamps frame to the [from, from + duration] window, so calling it with a frame outside that range holds the first or last defined state instead of throwing.
Loop behavior
loop: true repeats the animation on a duration-frame cycle, starting at from. This is intended for components like <Spin> or <Pulse> that are meant to repeat continuously — one-shot effects like <FadeIn> or <SlideInUp> typically shouldn't set loop, since they're designed to settle and hold their final state.
<Pulse from={0} duration={40} loop>
<Dot />
</Pulse>TypeScript
Kinety is written in TypeScript and ships its own .d.ts files — no @types package needed.
Contributing
Issues and PRs are welcome. If you're adding a new preset, add it to src/presets.ts and export a matching component from src/components.tsx following the existing createAnimatedComponent(name, keyframes) pattern.
License
MIT
