Instagram Reels Template (Remotion)
A 30-second vertical Instagram Reels template with dynamic text overlays, music note animation, reaction icons, and a progress bar — 1080×1920, 30 fps.
Preview
Code
import React from "react";
import {
AbsoluteFill,
Composition,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from "remotion";
// ── CONFIG ─────────────────────────────────────────────────────────────────
const CONFIG = {
// Creator
USERNAME: "@pixel.dev",
FOLLOWERS: "89.2K followers",
AVATAR_INITIALS: "PD",
AVATAR_COLOR: "#6c5ce7",
// Caption
CAPTION_WORDS: ["when", "the", "code", "finally", "works", "✨"],
HASHTAGS: "#coding #programmer #dev",
// Music
SONG_TITLE: "Lofi Vibes — ChillBeat Studio",
// Action icon counts
HEART_COUNT: "847K",
COMMENT_COUNT: "12.3K",
// Gradient colors (background)
COLOR_A: "#ff6b6b",
COLOR_B: "#feca57",
COLOR_C: "#48dbfb",
// Durations
TOTAL_FRAMES: 900,
FADE_OUT_START: 880,
} as const;
// ── Animated gradient background ───────────────────────────────────────────
const AnimatedBackground: React.FC<{ frame: number; totalFrames: number }> = ({
frame,
totalFrames,
}) => {
// Cycle through the three-stop gradient by shifting hue angle over time
const t = frame / totalFrames;
// Interpolate between color sets in two halves for a seamless loop feel
const r1 = interpolate(t, [0, 0.33, 0.66, 1], [255, 254, 72, 255]);
const g1 = interpolate(t, [0, 0.33, 0.66, 1], [107, 202, 219, 107]);
const b1 = interpolate(t, [0, 0.33, 0.66, 1], [107, 87, 251, 107]);
const r2 = interpolate(t, [0, 0.33, 0.66, 1], [254, 72, 255, 254]);
const g2 = interpolate(t, [0, 0.33, 0.66, 1], [202, 219, 107, 202]);
const b2 = interpolate(t, [0, 0.33, 0.66, 1], [87, 251, 107, 87]);
const topColor = `rgb(${Math.round(r1)},${Math.round(g1)},${Math.round(b1)})`;
const bottomColor = `rgb(${Math.round(r2)},${Math.round(g2)},${Math.round(b2)})`;
return (
<AbsoluteFill
style={{
background: `linear-gradient(160deg, ${topColor} 0%, ${CONFIG.COLOR_B} 50%, ${bottomColor} 100%)`,
}}
/>
);
};
// ── Top progress bar ───────────────────────────────────────────────────────
const ProgressBar: React.FC<{ frame: number; totalFrames: number }> = ({
frame,
totalFrames,
}) => {
const progress = interpolate(frame, [0, totalFrames], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 3,
backgroundColor: "rgba(255,255,255,0.25)",
zIndex: 50,
}}
>
<div
style={{
width: `${progress * 100}%`,
height: "100%",
backgroundColor: "#ffffff",
borderRadius: "0 2px 2px 0",
boxShadow: "0 0 6px rgba(255,255,255,0.7)",
}}
/>
</div>
);
};
// ── Creator header (avatar + username + followers) ─────────────────────────
const CreatorHeader: React.FC<{ frame: number; fps: number }> = ({
frame,
fps,
}) => {
const translateY = spring({
frame,
fps,
from: -30,
to: 0,
config: { damping: 18, stiffness: 120 },
});
const opacity = interpolate(frame, [0, 20], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div
style={{
position: "absolute",
top: 60,
left: 24,
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 14,
opacity,
transform: `translateY(${translateY}px)`,
zIndex: 20,
}}
>
{/* Avatar */}
<div
style={{
width: 56,
height: 56,
borderRadius: "50%",
backgroundColor: CONFIG.AVATAR_COLOR,
border: "2.5px solid #ffffff",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 20,
fontWeight: 700,
color: "#ffffff",
fontFamily: "system-ui, -apple-system, sans-serif",
flexShrink: 0,
}}
>
{CONFIG.AVATAR_INITIALS}
</div>
{/* Name block */}
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
<div
style={{
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 700,
fontSize: 20,
color: "#ffffff",
textShadow: "0 1px 4px rgba(0,0,0,0.4)",
}}
>
{CONFIG.USERNAME}
</div>
<div
style={{
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 400,
fontSize: 14,
color: "rgba(255,255,255,0.75)",
}}
>
{CONFIG.FOLLOWERS}
</div>
</div>
{/* Follow pill */}
<div
style={{
marginLeft: 8,
paddingInline: 18,
paddingBlock: 6,
borderRadius: 20,
backgroundColor: "#ffffff",
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 700,
fontSize: 14,
color: "#ff6b6b",
}}
>
Follow
</div>
</div>
);
};
// ── Right-side action icon ─────────────────────────────────────────────────
interface ActionIconProps {
frame: number;
fps: number;
emoji: string;
label: string;
delay: number;
offsetY: number;
pulse?: boolean;
}
const ActionIcon: React.FC<ActionIconProps> = ({
frame,
fps,
emoji,
label,
delay,
offsetY,
pulse = false,
}) => {
const f = Math.max(0, frame - delay);
const translateX = spring({
frame: f,
fps,
from: 80,
to: 0,
config: { damping: 16, stiffness: 100 },
});
const opacity = interpolate(f, [0, 18], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Subtle heartbeat pulse for the heart icon
const scale = pulse
? interpolate(
Math.sin((frame / fps) * Math.PI * 2 * 1.2),
[-1, 1],
[0.92, 1.08]
)
: 1;
return (
<div
style={{
position: "absolute",
right: 20,
bottom: offsetY,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 6,
opacity,
transform: `translateX(${translateX}px)`,
zIndex: 20,
}}
>
<div
style={{
width: 58,
height: 58,
borderRadius: "50%",
backgroundColor: "rgba(255,255,255,0.18)",
backdropFilter: "blur(4px)",
border: "1px solid rgba(255,255,255,0.25)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 28,
transform: `scale(${scale})`,
}}
>
{emoji}
</div>
<div
style={{
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 600,
fontSize: 13,
color: "#ffffff",
textShadow: "0 1px 3px rgba(0,0,0,0.5)",
}}
>
{label}
</div>
</div>
);
};
// ── Bottom caption ─────────────────────────────────────────────────────────
const CaptionOverlay: React.FC<{ frame: number; fps: number }> = ({
frame,
fps,
}) => {
const blockOpacity = interpolate(frame, [10, 35], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const blockY = spring({
frame,
fps,
from: 20,
to: 0,
config: { damping: 20, stiffness: 100 },
});
return (
<div
style={{
position: "absolute",
bottom: 130,
left: 20,
right: 96,
opacity: blockOpacity,
transform: `translateY(${blockY}px)`,
zIndex: 20,
}}
>
{/* Caption words stagger */}
<div
style={{
display: "flex",
flexWrap: "wrap",
columnGap: 8,
rowGap: 4,
marginBottom: 10,
}}
>
{CONFIG.CAPTION_WORDS.map((word, i) => {
const wordDelay = 20 + i * 10;
const wf = Math.max(0, frame - wordDelay);
const wordOpacity = interpolate(wf, [0, 12], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const wordY = spring({
frame: wf,
fps,
from: 10,
to: 0,
config: { damping: 18, stiffness: 120 },
});
return (
<span
key={i}
style={{
opacity: wordOpacity,
transform: `translateY(${wordY}px)`,
display: "inline-block",
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 700,
fontSize: 28,
color: "#ffffff",
textShadow: "0 2px 6px rgba(0,0,0,0.45)",
}}
>
{word}
</span>
);
})}
</div>
{/* Hashtags */}
<div
style={{
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 500,
fontSize: 18,
color: "rgba(255,255,255,0.8)",
textShadow: "0 1px 4px rgba(0,0,0,0.4)",
}}
>
{CONFIG.HASHTAGS}
</div>
</div>
);
};
// ── Music ticker ───────────────────────────────────────────────────────────
const MusicTicker: React.FC<{ frame: number }> = ({ frame }) => {
// Marquee: scroll from right to left over 240 frames, then loop
const loopFrame = frame % 240;
const translateX = interpolate(loopFrame, [0, 240], [60, -500], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Note icon rotation
const rotation = interpolate(frame, [0, 60], [0, 360], {
extrapolateLeft: "clamp",
extrapolateRight: "wrap",
});
const opacity = interpolate(frame, [5, 25], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div
style={{
position: "absolute",
bottom: 28,
left: 0,
right: 0,
height: 48,
overflow: "hidden",
opacity,
zIndex: 20,
}}
>
{/* Background strip */}
<div
style={{
position: "absolute",
inset: 0,
backgroundColor: "rgba(0,0,0,0.35)",
backdropFilter: "blur(6px)",
}}
/>
{/* Content row */}
<div
style={{
position: "absolute",
inset: 0,
display: "flex",
alignItems: "center",
paddingLeft: 16,
gap: 12,
}}
>
{/* Spinning note */}
<div
style={{
fontSize: 22,
transform: `rotate(${rotation}deg)`,
flexShrink: 0,
}}
>
♪
</div>
{/* Scrolling text */}
<div
style={{
whiteSpace: "nowrap",
transform: `translateX(${translateX}px)`,
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 500,
fontSize: 15,
color: "#ffffff",
}}
>
{CONFIG.SONG_TITLE} {CONFIG.SONG_TITLE} {CONFIG.SONG_TITLE}
</div>
</div>
</div>
);
};
// ── Global fade overlay ────────────────────────────────────────────────────
const FadeOut: React.FC<{ frame: number; totalFrames: number; fadeStart: number }> = ({
frame,
totalFrames,
fadeStart,
}) => {
const opacity = interpolate(frame, [fadeStart, totalFrames], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
if (opacity === 0) return null;
return (
<AbsoluteFill
style={{
backgroundColor: "#000000",
opacity,
zIndex: 100,
}}
/>
);
};
// ── Main composition ───────────────────────────────────────────────────────
export const InstagramReelsTemplate: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
return (
<AbsoluteFill style={{ backgroundColor: "#000" }}>
{/* Animated gradient background */}
<AnimatedBackground frame={frame} totalFrames={CONFIG.TOTAL_FRAMES} />
{/* Top progress bar */}
<ProgressBar frame={frame} totalFrames={CONFIG.TOTAL_FRAMES} />
{/* Creator header */}
<CreatorHeader frame={frame} fps={fps} />
{/* Right-side actions — staggered slide-in from right */}
<ActionIcon
frame={frame}
fps={fps}
emoji="❤️"
label={CONFIG.HEART_COUNT}
delay={15}
offsetY={480}
pulse
/>
<ActionIcon
frame={frame}
fps={fps}
emoji="💬"
label={CONFIG.COMMENT_COUNT}
delay={25}
offsetY={380}
/>
<ActionIcon
frame={frame}
fps={fps}
emoji="➤"
label="Share"
delay={35}
offsetY={280}
/>
<ActionIcon
frame={frame}
fps={fps}
emoji="🔖"
label="Save"
delay={45}
offsetY={180}
/>
{/* Caption */}
<CaptionOverlay frame={frame} fps={fps} />
{/* Music ticker */}
<MusicTicker frame={frame} />
{/* Fade to black at end */}
<FadeOut
frame={frame}
totalFrames={CONFIG.TOTAL_FRAMES}
fadeStart={CONFIG.FADE_OUT_START}
/>
</AbsoluteFill>
);
};
// ── Remotion Root ──────────────────────────────────────────────────────────
export const RemotionRoot: React.FC = () => (
<Composition
id="InstagramReelsTemplate"
component={InstagramReelsTemplate}
durationInFrames={CONFIG.TOTAL_FRAMES}
fps={30}
width={1080}
height={1920}
/>
);Instagram Reels Template
A fully animated 30-second vertical Instagram Reels template built with Remotion. The composition opens on a vivid gradient background that shifts colors across the full duration — cycling from coral red through warm yellow into sky blue — giving the clip energy without needing real footage. An animated top progress bar tracks playback in real time. The creator’s avatar circle and username fade and spring in from the top-left corner, immediately followed by a caption that builds word-by-word with a stagger typewriter effect and terminates with the hashtag line. On the right edge, four action icons — heart, comment, share, and save — stagger in from outside the frame with individual spring delays, each sitting inside a frosted circle with its counter label beneath. At the bottom a scrolling music ticker with a rotating note icon continuously marquees the song name across the full width, mimicking the native Instagram audio strip.
All configurable values — username, follower count, caption words, hashtags, song title, action counts, and gradient stops — are collected in a single CONFIG block at the top of the file for fast customization.
Composition specs
| Property | Value |
|---|---|
| Resolution | 1080 × 1920 |
| FPS | 30 |
| Duration | 30 s (900 frames) |
Elements
- Animated gradient background — three-stop color shift (#ff6b6b → #feca57 → #48dbfb) cycling continuously across the clip duration
- Top progress bar — thin 3 px bar at the very top tracking playback from 0 % to 100 %
- Creator header — avatar circle (initials placeholder) + username “@pixel.dev” + follower count spring-in from frame 0
- Caption overlay — “when the code finally works ✨” text with per-word stagger fade-in; hashtag line below in a lighter tint
- Right-side action icons — heart (❤️ 847K), comment (💬 12.3K), share (➤ Share), save (🔖 Save) staggered slide-in from the right edge
- Music ticker strip — bottom bar with a rotating ♪ note icon and a continuously scrolling song name marquee animation
- Global fade-out — final 20 frames fade the entire composition to black