Remotion — Create Videos with React and TypeScript (2026 Tutorial)

Videos, but make them React components. Remotion lets you create motion graphics, animated intros, data visualizations, and full video productions — entirely in TypeScript. No After Effects. No timeline editors. Just code.
What You Will Learn
By the end of this tutorial, you will:
- Understand Remotion's architecture and core concepts
- Set up a Remotion project from scratch with TypeScript
- Build animated compositions using React components
- Work with springs, interpolation, and audio
- Create a data-driven video that renders dynamic content
- Render your video as an MP4 file
- Deploy a render pipeline for automated video generation
Prerequisites
Before starting, ensure you have:
- Node.js 20+ installed (
node --version) - React and TypeScript knowledge (hooks, components, JSX)
- A code editor — VS Code recommended
- ffmpeg installed for rendering (
brew install ffmpegon macOS) - Basic understanding of CSS animations and transforms
Why Remotion?
Traditional video editing tools are great for one-off projects. But what if you need to:
- Generate hundreds of personalized videos for marketing campaigns
- Create data-driven visualizations that update automatically
- Build branded intros and outros that stay consistent across your content
- Produce social media clips at scale with dynamic text overlays
Remotion solves this by treating video frames as React components. Each frame is a function of time — you write JSX, and Remotion renders it frame by frame into a real video file.
Key Concepts
| Concept | Description |
|---|---|
| Composition | A video definition with width, height, FPS, and duration |
| Sequence | A time-shifted wrapper that delays when a component appears |
| useCurrentFrame() | Hook that returns the current frame number |
| useVideoConfig() | Hook that returns video metadata (width, height, fps, duration) |
| interpolate() | Maps a frame range to an output range (like CSS keyframes) |
| spring() | Physics-based animation curve for natural motion |
Step 1: Project Setup
Create a new Remotion project using the official starter:
npx create-video@latest my-video --template blank
cd my-videoThis scaffolds a TypeScript project with everything configured. Let's look at the structure:
my-video/
├── src/
│ ├── Root.tsx # Entry point — registers compositions
│ ├── Composition.tsx # Your first video component
│ └── index.ts # Remotion entry
├── remotion.config.ts # Remotion configuration
├── package.json
└── tsconfig.json
Install dependencies and start the preview server:
npm install
npm startThis opens the Remotion Studio at http://localhost:3000 — a browser-based preview environment where you can scrub through your video timeline in real time.
Step 2: Understanding the Composition
Open src/Root.tsx. This is where you register your video compositions:
import { Composition } from "remotion";
import { MyComposition } from "./Composition";
export const RemotionRoot: React.FC = () => {
return (
<>
<Composition
id="MyVideo"
component={MyComposition}
durationInFrames={150}
fps={30}
width={1920}
height={1080}
/>
</>
);
};Key properties:
- id — Unique identifier used when rendering
- durationInFrames — Total frames (150 frames at 30fps = 5 seconds)
- fps — Frames per second (30 is standard, 60 for smooth motion)
- width/height — Video resolution in pixels
Step 3: Building Your First Animation
Replace src/Composition.tsx with an animated title card:
import { useCurrentFrame, useVideoConfig, interpolate, spring } from "remotion";
import React from "react";
export const MyComposition: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Spring animation for the title
const titleScale = spring({
frame,
fps,
config: {
damping: 12,
stiffness: 200,
mass: 0.5,
},
});
// Fade in the subtitle after 20 frames
const subtitleOpacity = interpolate(frame, [20, 40], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Slide the subtitle up
const subtitleY = interpolate(frame, [20, 40], [30, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div
style={{
flex: 1,
background: "linear-gradient(135deg, #0f0c29, #302b63, #24243e)",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
fontFamily: "Inter, sans-serif",
}}
>
<h1
style={{
fontSize: 80,
color: "white",
fontWeight: 900,
transform: `scale(${titleScale})`,
textAlign: "center",
}}
>
Hello, Remotion
</h1>
<p
style={{
fontSize: 32,
color: "#a8a8ff",
opacity: subtitleOpacity,
transform: `translateY(${subtitleY}px)`,
marginTop: 20,
}}
>
Videos powered by React
</p>
</div>
);
};Save the file and check the Remotion Studio — you will see a smooth animation where the title bounces in with a spring effect, followed by a subtitle that fades and slides up.
How It Works
useCurrentFrame()returns the current frame (0, 1, 2, ... 149)spring()creates a physics-based curve that starts at 0 and settles at 1interpolate()maps frame ranges to output values — frame 20-40 maps opacity from 0 to 1extrapolateLeft/Right: "clamp"prevents values from going below 0 or above 1
Step 4: Using Sequences for Timing
Sequences let you compose multiple animated sections with precise timing:
import {
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
AbsoluteFill,
} from "remotion";
import React from "react";
const Title: React.FC<{ text: string }> = ({ text }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const scale = spring({ frame, fps, config: { damping: 12 } });
const opacity = interpolate(frame, [0, 15], [0, 1], {
extrapolateRight: "clamp",
});
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<h1
style={{
fontSize: 72,
color: "white",
transform: `scale(${scale})`,
opacity,
fontWeight: 800,
}}
>
{text}
</h1>
</AbsoluteFill>
);
};
const FadeOut: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 10], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return <AbsoluteFill style={{ opacity }}>{children}</AbsoluteFill>;
};
export const MyComposition: React.FC = () => {
return (
<AbsoluteFill
style={{
background: "linear-gradient(135deg, #0f0c29, #302b63, #24243e)",
}}
>
{/* Scene 1: Intro (frames 0-59) */}
<Sequence from={0} durationInFrames={60}>
<Title text="Welcome" />
</Sequence>
{/* Scene 2: Main message (frames 45-119) */}
<Sequence from={45} durationInFrames={75}>
<Title text="Build Videos with Code" />
</Sequence>
{/* Scene 3: Outro with fade (frames 110-150) */}
<Sequence from={110} durationInFrames={40}>
<FadeOut>
<Title text="Let's Go!" />
</FadeOut>
</Sequence>
</AbsoluteFill>
);
};Notice how Sequence resets useCurrentFrame() to 0 within its children. This means each component starts its animation from frame 0, regardless of when it appears in the timeline. Sequences can overlap to create smooth transitions.
Step 5: Adding Audio
Remotion supports audio with precise frame-level synchronization:
import { Audio, Sequence, staticFile } from "remotion";
// Place your audio file in the /public folder
// e.g., public/music.mp3
export const MyComposition: React.FC = () => {
return (
<AbsoluteFill style={{ background: "#0f0c29" }}>
{/* Background music starting at frame 0 */}
<Audio src={staticFile("music.mp3")} volume={0.3} />
{/* Sound effect at frame 30 */}
<Sequence from={30}>
<Audio src={staticFile("whoosh.mp3")} volume={0.8} />
</Sequence>
{/* Your visual content */}
<Sequence from={0}>
<Title text="With Sound!" />
</Sequence>
</AbsoluteFill>
);
};You can also dynamically control volume based on the current frame:
<Audio
src={staticFile("music.mp3")}
volume={(f) =>
interpolate(f, [0, 30, 120, 150], [0, 0.5, 0.5, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
})
}
/>This fades the music in over the first second and out over the last second.
Step 6: Data-Driven Videos
This is where Remotion really shines. Let's build a video that renders dynamic data — imagine generating weekly stats videos automatically:
import React from "react";
import {
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
AbsoluteFill,
} from "remotion";
interface StatsProps {
title: string;
stats: Array<{ label: string; value: number; color: string }>;
}
const AnimatedBar: React.FC<{
label: string;
value: number;
maxValue: number;
color: string;
delay: number;
}> = ({ label, value, maxValue, color, delay }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({
frame: frame - delay,
fps,
config: { damping: 15, stiffness: 100 },
});
const width = interpolate(progress, [0, 1], [0, (value / maxValue) * 100]);
const labelOpacity = interpolate(frame, [delay, delay + 10], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div style={{ marginBottom: 24, opacity: labelOpacity }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 8,
fontSize: 24,
color: "white",
fontFamily: "Inter, sans-serif",
}}
>
<span>{label}</span>
<span style={{ fontWeight: 700 }}>
{Math.round(value * progress)}
</span>
</div>
<div
style={{
height: 32,
background: "rgba(255,255,255,0.1)",
borderRadius: 16,
overflow: "hidden",
}}
>
<div
style={{
height: "100%",
width: `${width}%`,
background: `linear-gradient(90deg, ${color}, ${color}aa)`,
borderRadius: 16,
}}
/>
</div>
</div>
);
};
export const StatsVideo: React.FC<StatsProps> = ({ title, stats }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const titleScale = spring({ frame, fps, config: { damping: 12 } });
const maxValue = Math.max(...stats.map((s) => s.value));
return (
<AbsoluteFill
style={{
background: "linear-gradient(180deg, #1a1a2e, #16213e)",
padding: 80,
justifyContent: "center",
}}
>
<h1
style={{
fontSize: 56,
color: "white",
fontWeight: 900,
marginBottom: 60,
transform: `scale(${titleScale})`,
fontFamily: "Inter, sans-serif",
}}
>
{title}
</h1>
{stats.map((stat, i) => (
<AnimatedBar
key={stat.label}
label={stat.label}
value={stat.value}
maxValue={maxValue}
color={stat.color}
delay={i * 15 + 10}
/>
))}
</AbsoluteFill>
);
};Register the composition with default props in Root.tsx:
<Composition
id="WeeklyStats"
component={StatsVideo}
durationInFrames={180}
fps={30}
width={1080}
height={1080}
defaultProps={{
title: "This Week's Performance",
stats: [
{ label: "New Users", value: 1247, color: "#6366f1" },
{ label: "Page Views", value: 8432, color: "#22d3ee" },
{ label: "Conversions", value: 342, color: "#f43f5e" },
{ label: "Revenue ($)", value: 5890, color: "#10b981" },
],
}}
/>Now you have a reusable stats video component. Change the props and you get a completely different video — perfect for automation.
Step 7: Loading Custom Fonts
Use @remotion/google-fonts or load fonts manually:
npm install @remotion/google-fontsimport { loadFont } from "@remotion/google-fonts/Inter";
const { fontFamily } = loadFont();
// Use in your components
<h1 style={{ fontFamily }}>Styled with Inter</h1>For local fonts, place them in the public/ folder and use staticFile():
import { staticFile } from "remotion";
const fontFace = new FontFace("MyFont", `url(${staticFile("fonts/MyFont.woff2")})`);
// Load in your component
React.useEffect(() => {
document.fonts.add(fontFace);
fontFace.load();
}, []);Step 8: Rendering Your Video
Preview Rendering
The Remotion Studio lets you preview in real time. Use the timeline to scrub, and the play button to watch at full speed.
CLI Rendering
Render your video as an MP4 from the command line:
npx remotion render MyVideo out/my-video.mp4Options you can customize:
npx remotion render MyVideo out/my-video.mp4 \
--codec h264 \
--quality 80 \
--concurrency 4| Flag | Description |
|---|---|
--codec | h264 (MP4), vp8/vp9 (WebM), prores (MOV) |
--quality | 0-100, JPEG quality for frames |
--concurrency | Number of parallel rendering threads |
--props | JSON string to override default props |
Rendering with Custom Props
Pass data at render time to create different videos from the same template:
npx remotion render WeeklyStats out/stats-week-14.mp4 \
--props='{"title":"Week 14 Stats","stats":[{"label":"Users","value":2000,"color":"#6366f1"}]}'Step 9: Rendering via Node.js API
For automated pipelines, use the programmatic API:
import { bundle } from "@remotion/bundler";
import { renderMedia, selectComposition } from "@remotion/renderer";
import path from "path";
async function renderVideo() {
// Bundle the Remotion project
const bundleLocation = await bundle({
entryPoint: path.resolve("./src/index.ts"),
});
// Select the composition
const composition = await selectComposition({
serveUrl: bundleLocation,
id: "WeeklyStats",
inputProps: {
title: "April 2026 Report",
stats: [
{ label: "Revenue", value: 12500, color: "#10b981" },
{ label: "Users", value: 3400, color: "#6366f1" },
],
},
});
// Render to MP4
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: "h264",
outputLocation: "out/april-report.mp4",
onProgress: ({ progress }) => {
console.log(`Rendering: ${Math.round(progress * 100)}%`);
},
});
console.log("Video rendered successfully!");
}
renderVideo();This is perfect for:
- CI/CD pipelines that generate release announcement videos
- Cron jobs that produce daily/weekly report videos
- API endpoints that create personalized videos on demand
Step 10: Advanced Techniques
Easing Functions
Remotion provides built-in easing curves:
import { Easing, interpolate } from "remotion";
const value = interpolate(frame, [0, 30], [0, 1], {
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
extrapolateRight: "clamp",
});Staggered Animations
Create staggered entrance effects by offsetting delays:
const items = ["React", "TypeScript", "Remotion"];
return (
<>
{items.map((item, i) => {
const delay = i * 10;
const opacity = interpolate(frame, [delay, delay + 15], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const x = interpolate(frame, [delay, delay + 15], [-50, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<p
key={item}
style={{
opacity,
transform: `translateX(${x}px)`,
fontSize: 36,
color: "white",
}}
>
{item}
</p>
);
})}
</>
);Using Images and Videos
Remotion can composite images and embed other videos:
import { Img, Video, staticFile } from "remotion";
// Static image
<Img src={staticFile("logo.png")} style={{ width: 200 }} />
// Embed a video
<Video src={staticFile("background.mp4")} />
// Remote image (fetched during render)
<Img src="https://example.com/chart.png" />Responsive Sizing
Use useVideoConfig() for responsive layouts that work across different resolutions:
const { width, height } = useVideoConfig();
<div
style={{
fontSize: width * 0.04, // 4% of video width
padding: width * 0.05, // 5% padding
}}
>
Scales with resolution
</div>Testing Your Implementation
- Start the preview server:
npm startand verify all animations play correctly - Check timing: Use the timeline scrubber to verify sequence transitions
- Test rendering: Run
npx remotion render MyVideo out/test.mp4and play the output - Verify audio sync: If using audio, check that sound effects align with visual cues
- Test with different props: Pass various data sets to your data-driven compositions
Troubleshooting
Common Issues
"ffmpeg not found"
Install ffmpeg: brew install ffmpeg (macOS) or apt install ffmpeg (Ubuntu).
Slow rendering
Increase concurrency with --concurrency 8. Each thread renders frames in parallel.
Fonts not loading
Ensure fonts are loaded before the first frame renders. Use @remotion/google-fonts for reliable font loading, or preload custom fonts with delayRender():
import { delayRender, continueRender } from "remotion";
const [handle] = React.useState(() => delayRender());
React.useEffect(() => {
const font = new FontFace("MyFont", `url(${staticFile("font.woff2")})`);
font.load().then(() => {
document.fonts.add(font);
continueRender(handle);
});
}, [handle]);Black frames at the start
This usually means an async resource (image, font, data) has not loaded yet. Use delayRender() and continueRender() to pause rendering until everything is ready.
Project Ideas
Now that you know the basics, here are some ideas to build:
- YouTube intro/outro templates — Branded animations for your channel
- Social media story generator — Dynamically fill product info into story templates
- Code walkthrough videos — Animate code appearing line by line with syntax highlighting
- Event countdown timers — Animated countdown to a launch date
- Invoice/receipt videos — Animated financial summaries for clients
- Podcast audiograms — Waveform visualizations synced to podcast audio
Next Steps
- Explore the Remotion documentation for advanced features like
<OffthreadVideo>, Lambda rendering, and Player embedding - Check out Remotion templates for production-ready starting points
- Learn about Remotion Lambda for rendering videos at scale on AWS
- Combine with Motion (Framer Motion) for complex UI animations inside your videos
Conclusion
Remotion transforms video creation from a manual, creative-tool-heavy process into a programmable, repeatable, and scalable workflow. By treating every frame as a React component, you get the full power of TypeScript, npm packages, and component composition — applied to video.
Whether you are building automated marketing content, data visualizations, or branded templates, Remotion gives you the developer experience you already know and love. Start with simple animations, compose them into sequences, and before long you will be rendering production-quality videos entirely from code.
Discuss Your Project with Us
We're here to help with your web development needs. Schedule a call to discuss your project and how we can assist you.
Let's find the best solutions for your needs.
Related Articles

Capacitor + React — Build Cross-Platform Mobile Apps from Your Web App (2026)
Turn your React web app into a native iOS and Android app using Capacitor. This hands-on tutorial covers project setup, native plugins, camera access, local storage, deployment to app stores, and production-ready patterns.

Building Local-First Collaborative Apps with Yjs and React
Learn how to build real-time collaborative applications that work offline using Yjs CRDTs and React. This tutorial covers conflict-free data synchronization, offline-first architecture, and building a shared document editor from scratch.

Authenticate Your Next.js 15 App with Auth.js v5: Email, OAuth, and Role-Based Access
Learn how to add production-ready authentication to your Next.js 15 application using Auth.js v5. This comprehensive guide covers Google OAuth, email/password credentials, protected routes, middleware, and role-based access control.