Remotion video creation - programmatically create videos using React. Use for: (1) animated videos, motion graphics, or video content from code, (2) data-driven or templated video generation systems, (3) social media videos, product demos, explainer animations, marketing content, (4) .mp4, .webm, .gif, or image sequence outputs, (5) React-based video compositions with animations, transitions, and effects. Covers Composition, Sequence, useCurrentFrame, useVideoConfig, interpolate, spring animations, and npx remotion render.
Create videos programmatically using React components. Each frame is a React render; animations derive from the current frame number.
| Task | Resource |
|---|---|
| Project setup | Setup Commands |
| Create composition | Core Concepts |
| Add animations | Animation Patterns / references/animation-patterns.md |
| API lookup | Essential APIs / |
| Render video | Rendering Commands |
| Debug issues | references/troubleshooting.md |
The video container defining dimensions and duration:
<Composition
id="MyVideo"
component={MyComponent}
durationInFrames={300} // 10 seconds at 30fps
fps={30}
width={1920}
height={1080}
/>
Time unit in Remotion. Frame 0 = start, durationInFrames - 1 = end.
seconds * fpsframe / fpsTime-shift children to start at a specific frame:
<Sequence from={30} durationInFrames={60}>
<SceneTwo /> {/* Appears at frame 30, lasts 60 frames */}
</Sequence>
useCurrentFrame() to get current frameuseState for animation statenpx create-video@latest my-video
cd my-video
npm run dev
npm install remotion @remotion/cli
npx remotion studio
# or
npm run dev
| API | Purpose | Example |
|---|---|---|
useCurrentFrame() | Get current frame (0-indexed) | const frame = useCurrentFrame(); |
useVideoConfig() | Get fps, width, height, duration | const { fps, durationInFrames } = useVideoConfig(); |
interpolate() | Map frame range to value range | interpolate(frame, [0, 30], [0, 1]) |
spring() | Physics-based animation | spring({ frame, fps, config: { damping: 10 } }) |
<Sequence> | Time-shift children | <Sequence from={30}>...</Sequence> |
<Series> | Sequential clips | <Series><Series.Sequence>...</Series.Sequence></Series> |
<AbsoluteFill> | Full-frame container | <AbsoluteFill style={{ backgroundColor: '#000' }}> |
<Img> | Optimized image | <Img src={staticFile('logo.png')} /> |
<Video> | Embed video | <Video src={staticFile('clip.mp4')} /> |
<Audio> | Embed audio | <Audio src={staticFile('music.mp3')} /> |
staticFile() | Reference public/ files | staticFile('assets/image.png') |
See references/api-reference.md for complete API documentation.
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 30], [0, 1], {
extrapolateRight: 'clamp',
});
return <div style={{ opacity }}>Content</div>;
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const scale = spring({ frame, fps, config: { damping: 12, stiffness: 200 } });
return <div style={{ transform: `scale(${scale})` }}>Content</div>;
const frame = useCurrentFrame();
const x = interpolate(frame, [0, 30], [-100, 0], {
extrapolateRight: 'clamp',
});
return <div style={{ transform: `translateX(${x}%)` }}>Content</div>;
const items = ['One', 'Two', 'Three'];
return (
<AbsoluteFill>
{items.map((item, i) => (
<Sequence key={item} from={i * 15}>
<FadeIn>{item}</FadeIn>
</Sequence>
))}
</AbsoluteFill>
);
const frame = useCurrentFrame();
const text = "Hello, World!";
const chars = Math.floor(interpolate(frame, [0, 60], [0, text.length], {
extrapolateRight: 'clamp',
}));
return <span>{text.slice(0, chars)}</span>;
See references/animation-patterns.md for more patterns.
// MyVideo.tsx
import { AbsoluteFill, Sequence } from 'remotion';
import { Intro } from './Intro';
import { MainContent } from './MainContent';
import { Outro } from './Outro';
export const MyVideo: React.FC = () => {
return (
<AbsoluteFill style={{ backgroundColor: '#0a0a0a' }}>
<Sequence from={0} durationInFrames={90}>
<Intro />
</Sequence>
<Sequence from={90} durationInFrames={180}>
<MainContent />
</Sequence>
<Sequence from={270}>
<Outro />
</Sequence>
</AbsoluteFill>
);
};
import { Composition } from 'remotion';
import { MyVideo } from './MyVideo';
export const RemotionRoot: React.FC = () => {
return (
<>
<Composition
id="MyVideo"
component={MyVideo}
durationInFrames={300}
fps={30}
width={1920}
height={1080}
/>
<Composition
id="MyVideoSquare"
component={MyVideo}
durationInFrames={300}
fps={30}
width={1080}
height={1080}
/>
</>
);
};
type VideoProps = {
title: string;
backgroundColor: string;
};
export const TemplatedVideo: React.FC<VideoProps> = ({ title, backgroundColor }) => {
return (
<AbsoluteFill style={{ backgroundColor }}>
<h1>{title}</h1>
</AbsoluteFill>
);
};
// In Root.tsx
<Composition
id="TemplatedVideo"
component={TemplatedVideo}
durationInFrames={150}
fps={30}
width={1920}
height={1080}
defaultProps={{
title: 'Default Title',
backgroundColor: '#000',
}}
/>
import { delayRender, continueRender } from 'remotion';
import { useEffect, useState } from 'react';
export const DataDrivenVideo: React.FC = () => {
const [data, setData] = useState(null);
const [handle] = useState(() => delayRender('Loading data'));
useEffect(() => {
fetch('/api/data')
.then((res) => res.json())
.then((json) => {
setData(json);
continueRender(handle);
});
}, [handle]);
if (!data) return null;
return <div>{data.content}</div>;
};
npx remotion render src/index.ts MyVideo out/video.mp4
npx remotion render src/index.ts MyVideo out/video.mp4 \
--codec=h264 \
--crf=18 \
--scale=1
npx remotion render src/index.ts MyVideo out/video.webm --codec=vp8
npx remotion render src/index.ts MyVideo out/video.gif --codec=gif
npx remotion still src/index.ts MyVideo out/thumbnail.png --frame=0
npx remotion render src/index.ts MyVideo out/frames/ --image-format=png --sequence
npx remotion render src/index.ts TemplatedVideo out/video.mp4 \
--props='{"title":"Custom Title","backgroundColor":"#1a1a2e"}'
npx remotion render src/index.ts MyVideo test.mp4 --frames=0-30
npx remotion render src/index.ts MyVideo out/video.mp4 --concurrency=8
| Format | Width | Height | Use Case |
|---|---|---|---|
| 1080p | 1920 | 1080 | YouTube, general |
| 4K | 3840 | 2160 | High quality |
| Square | 1080 | 1080 | |
| Portrait | 1080 | 1920 | TikTok, Reels |
| 1280 | 720 | Twitter video |
AbsoluteFill for layering elements@remotion/google-fonts or explicit loadingstaticFile() for all assets in public/OffthreadVideo for memory-efficient video embeddingnpm run dev
# Opens Remotion Studio at http://localhost:3000
npx remotion compositions src/index.ts
# Render first 30 frames only
npx remotion render src/index.ts MyVideo test.mp4 --frames=0-30
# Render at lower scale
npx remotion render src/index.ts MyVideo test.mp4 --scale=0.5
startFrom and endAt props on <Video> and <Audio> for trimming| Use Case | Key Components |
|---|---|
| Product demo | <Video>, <Sequence>, text overlays |
| Social media | Square/portrait compositions, quick animations |
| Data viz | interpolate() for animated charts |
| Title sequence | Spring animations, staggered text |
| Slideshow | <Series>, <Img>, transitions |
| Tutorial | Screen recordings with callouts |
| Music visualizer | Audio analysis, reactive animations |
my-video/
├── src/
│ ├── index.ts # Entry point
│ ├── Root.tsx # Composition registrations
│ ├── MyVideo.tsx # Main composition
│ └── components/ # Reusable components
│ ├── Intro.tsx
│ ├── Title.tsx
│ └── animations/
├── public/ # Static assets (use staticFile())
│ ├── logo.png
│ └── music.mp3
├── remotion.config.ts # Remotion configuration
└── package.json