Generate professional walkthrough videos from app screenshots or live sites using Remotion. Smooth transitions, zoom effects, text overlays, and optional voiceover narration. Produces MP4 videos for demos, product showcases, or documentation. Triggers: 'walkthrough video', 'demo video', 'product video', 'create a video walkthrough', 'remotion video', 'screen recording', 'app demo', 'showcase video', 'generate video from screenshots'.
Create professional walkthrough videos from app screenshots or live sites using Remotion. Produces smooth, polished MP4 videos with transitions, zoom effects, and text overlays.
This skill takes a set of screenshots (or captures them from a running app) and orchestrates them into a Remotion video composition with:
Choose one approach:
If the user already has screenshots (e.g. from design-loop or product-showcase):
Read screenshots from:
- .design/screenshots/
- .jez/screenshots/
- User-specified directory
Sort them in walkthrough order (alphabetically by filename, or as user specifies).
If the app is running locally:
video/public/screens/playwright-cli -s=walkthrough open http://localhost:3000
playwright-cli -s=walkthrough resize 1280 720
playwright-cli -s=walkthrough screenshot --filename=video/public/screens/01-home.png
# Navigate to next page...
playwright-cli -s=walkthrough screenshot --filename=video/public/screens/02-dashboard.png
Same as Option B but with a public URL. Screenshot each key page.
Build a screens.json describing the walkthrough:
{
"projectName": "My App Walkthrough",
"fps": 30,
"width": 1280,
"height": 720,
"screens": [
{
"id": "home",
"title": "Welcome to MyApp",
"description": "The landing page introduces the core value proposition",
"imagePath": "screens/01-home.png",
"durationSeconds": 4,
"transition": "fade",
"zoomTarget": null
},
{
"id": "dashboard",
"title": "Your Dashboard",
"description": "See all your projects at a glance",
"imagePath": "screens/02-dashboard.png",
"durationSeconds": 5,
"transition": "slide-left",
"zoomTarget": { "x": 100, "y": 200, "width": 400, "height": 300, "delay": 2 }
}
]
}
| Field | Type | Description |
|---|---|---|
id | string | Unique screen identifier |
title | string | Text overlay title |
description | string | Subtitle or narration text |
imagePath | string | Path relative to video/public/ |
durationSeconds | number | How long to show this screen |
transition | string | fade, slide-left, slide-right, slide-up, wipe, none |
zoomTarget | object/null | If set, zoom into this region after delay seconds |
If no Remotion project exists:
mkdir -p video
cd video
npm init -y
npm install remotion @remotion/cli @remotion/transitions react react-dom
npm install -D typescript @types/react
Create the project structure:
video/
├── src/
│ ├── Root.tsx # Remotion entry point
│ ├── WalkthroughComposition.tsx # Main composition
│ ├── components/
│ │ ├── ScreenSlide.tsx # Individual screen display
│ │ ├── TextOverlay.tsx # Title/description overlay
│ │ ├── ProgressBar.tsx # Walkthrough progress indicator
│ │ └── ZoomEffect.tsx # Zoom into regions
│ └── config.ts # Load screens.json, calculate durations
├── public/
│ └── screens/ # Screenshot assets
│ ├── 01-home.png
│ └── 02-dashboard.png
├── screens.json # Screen manifest
├── remotion.config.ts
├── tsconfig.json
└── package.json
Generate each component file. Key patterns:
import { Composition } from "remotion";
import { WalkthroughComposition } from "./WalkthroughComposition";
import { screens, totalDurationInFrames, FPS, WIDTH, HEIGHT } from "./config";
export const RemotionRoot = () => (
<Composition
id="Walkthrough"
component={WalkthroughComposition}
durationInFrames={totalDurationInFrames}
fps={FPS}
width={WIDTH}
height={HEIGHT}
defaultProps={{ screens }}
/>
);
import { AbsoluteFill, Img, spring, useCurrentFrame, useVideoConfig } from "remotion";
interface ScreenSlideProps {
imageSrc: string;
title: string;
description: string;
}
export const ScreenSlide: React.FC<ScreenSlideProps> = ({ imageSrc, title, description }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Fade in
const opacity = spring({ frame, fps, config: { damping: 20 } });
// Subtle zoom (Ken Burns effect)
const scale = 1 + frame * 0.0002;
return (
<AbsoluteFill style={{ backgroundColor: "#000" }}>
<Img
src={imageSrc}
style={{
width: "100%",
height: "100%",
objectFit: "contain",
opacity,
transform: `scale(${scale})`,
}}
/>
{/* Text overlay at bottom */}
<div style={{
position: "absolute",
bottom: 40,
left: 40,
right: 40,
opacity: spring({ frame: frame - 15, fps, config: { damping: 20 } }),
}}>
<h2 style={{ color: "#fff", fontSize: 32, fontWeight: 700, textShadow: "0 2px 8px rgba(0,0,0,0.8)" }}>
{title}
</h2>
<p style={{ color: "#ccc", fontSize: 18, textShadow: "0 1px 4px rgba(0,0,0,0.8)" }}>
{description}
</p>
</div>
</AbsoluteFill>
);
};
Use @remotion/transitions for transitions:
import { TransitionSeries } from "@remotion/transitions";
import { fade } from "@remotion/transitions/fade";
import { slide } from "@remotion/transitions/slide";
// In WalkthroughComposition:
<TransitionSeries>
{screens.map((screen, i) => (
<TransitionSeries.Sequence
key={screen.id}
durationInFrames={screen.durationSeconds * FPS}
>
<ScreenSlide {...screen} />
</TransitionSeries.Sequence>
// Add transition between screens (not after last)
{i < screens.length - 1 && (
<TransitionSeries.Transition
presentation={getTransition(screens[i + 1].transition)}
timing={springTiming({ config: { damping: 20 }, durationInFrames: 15 })}
/>
)}
))}
</TransitionSeries>
cd video
npx remotion studio
This opens a browser-based preview. Check:
cd video
npx remotion render Walkthrough output.mp4 --codec h264
For higher quality:
npx remotion render Walkthrough output.mp4 --codec h264 --quality 90
For web-optimised (smaller file):
npx remotion render Walkthrough output.webm --codec vp8
Zoom into a specific area of the screen to highlight a feature:
// In ZoomEffect.tsx — interpolate scale and translate
const zoomScale = interpolate(frame, [delayFrames, delayFrames + 30], [1, 2.5], {
extrapolateRight: "clamp",
});
const translateX = interpolate(frame, [delayFrames, delayFrames + 30], [0, -targetX], {
extrapolateRight: "clamp",
});
Draw attention to UI elements:
// Pulsing circle that appears at a specific point
const scale = spring({ frame: frame - delay, fps, config: { damping: 8, stiffness: 80 } });
<div style={{
position: "absolute",
left: x - 20, top: y - 20,
width: 40, height: 40,
borderRadius: "50%",
border: "3px solid #3B82F6",
transform: `scale(${scale})`,
opacity: Math.min(1, scale),
}} />
Add a subtle background track:
import { Audio } from "remotion";
<Audio src={staticFile("music/background.mp3")} volume={0.15} />
Add title card at start and CTA at end:
// First sequence: Title card (3 seconds)
<TransitionSeries.Sequence durationInFrames={90}>
<TitleCard projectName="MyApp" tagline="The future of project management" />
</TransitionSeries.Sequence>
// ... screen sequences ...
// Last sequence: CTA card (4 seconds)
<TransitionSeries.Sequence durationInFrames={120}>
<CtaCard url="myapp.com" text="Try it free" />
</TransitionSeries.Sequence>
| Name | Effect | Best for |
|---|---|---|
fade | Cross-fade dissolve | Default, works everywhere |
slide-left | New screen slides in from right | Sequential flow (next page) |
slide-right | New screen slides in from left | Going back |
slide-up | New screen slides in from bottom | Drill-down into detail |
wipe | Wipe transition | Dramatic reveal |
none | Hard cut | Quick comparison |
| Format | Command | Use case |
|---|---|---|
| MP4 (H.264) | --codec h264 | Universal compatibility |
| WebM (VP8) | --codec vp8 | Web embedding, smaller files |
| GIF | --image-format png then ffmpeg | Short loops, social media |
| PNG sequence | --image-format png --sequence | Post-production editing |
npx remotion studio saves time vs full rendersstaticFile() for assets in public/ directory