Remotion — UI Walkthrough Video
A UI walkthrough video built with Remotion that guides viewers through four stages of a user flow — each stage presents a mock UI panel sliding in, annotated with floating callout labels and arrows pointing to key elements. A top progress bar and stage indicator track the viewer's position. Useful for documentation videos, sales presentations, and product education content.
Preview
Code
import React from "react";
import {
AbsoluteFill,
Composition,
Sequence,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from "remotion";
// ─── Design tokens ────────────────────────────────────────────────────────────
const BG = "#0a0a12";
const INDIGO = "#6366f1";
const INDIGO_LIGHT = "#818cf8";
const INDIGO_DIM = "#312e81";
const GREEN = "#22c55e";
const GREEN_DIM = "#14532d";
const SURFACE = "#13131f";
const SURFACE2 = "#1a1a2e";
const BORDER = "rgba(99,102,241,0.18)";
const TEXT = "#f1f5f9";
const TEXT_MUTED = "#94a3b8";
const TEXT_DIM = "#475569";
const FONT = "system-ui, -apple-system, sans-serif";
// ─── Helpers ──────────────────────────────────────────────────────────────────
function useFadeIn(startFrame: number, duration = 12): number {
const frame = useCurrentFrame();
return interpolate(frame, [startFrame, startFrame + duration], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
}
function useSlideUp(
startFrame: number,
distance = 24,
mass = 0.5
): { opacity: number; translateY: number } {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const s = spring({ frame: frame - startFrame, fps, config: { mass, stiffness: 120, damping: 18 } });
const opacity = interpolate(frame, [startFrame, startFrame + 10], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return { opacity, translateY: (1 - s) * distance };
}
// ─── Sub-component: GridOverlay ───────────────────────────────────────────────
function GridOverlay() {
const lines: React.ReactNode[] = [];
const cols = 12;
const rows = 8;
for (let i = 0; i <= cols; i++) {
lines.push(
<line
key={`v${i}`}
x1={`${(i / cols) * 100}%`}
y1="0%"
x2={`${(i / cols) * 100}%`}
y2="100%"
stroke="rgba(99,102,241,0.06)"
strokeWidth="1"
/>
);
}
for (let j = 0; j <= rows; j++) {
lines.push(
<line
key={`h${j}`}
x1="0%"
y1={`${(j / rows) * 100}%`}
x2="100%"
y2={`${(j / rows) * 100}%`}
stroke="rgba(99,102,241,0.06)"
strokeWidth="1"
/>
);
}
return (
<svg
style={{ position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" }}
>
{lines}
</svg>
);
}
// ─── Sub-component: RadialGlow ────────────────────────────────────────────────
function RadialGlow({ color = INDIGO, cx = "50%", cy = "50%", r = "55%" }) {
return (
<div
style={{
position: "absolute",
inset: 0,
background: `radial-gradient(ellipse ${r} ${r} at ${cx} ${cy}, ${color}14 0%, transparent 70%)`,
pointerEvents: "none",
}}
/>
);
}
// ─── Sub-component: StageIndicator ───────────────────────────────────────────
interface StageIndicatorProps {
currentStage: number; // 1-indexed, 1–4
opacity: number;
}
const STAGE_LABELS = ["Review Cart", "Shipping Info", "Payment", "Confirmation"];
function StageIndicator({ currentStage, opacity }: StageIndicatorProps) {
return (
<div
style={{
position: "absolute",
top: 32,
left: "50%",
transform: "translateX(-50%)",
display: "flex",
alignItems: "center",
gap: 0,
opacity,
}}
>
{STAGE_LABELS.map((label, i) => {
const stageNum = i + 1;
const isDone = stageNum < currentStage;
const isActive = stageNum === currentStage;
const isPending = stageNum > currentStage;
return (
<React.Fragment key={label}>
{/* Connector line */}
{i > 0 && (
<div
style={{
width: 48,
height: 2,
background: isDone || isActive ? INDIGO : "rgba(255,255,255,0.1)",
transition: "background 0.3s",
}}
/>
)}
{/* Pill */}
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }}>
<div
style={{
width: 36,
height: 36,
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontFamily: FONT,
fontSize: 14,
fontWeight: 700,
background: isDone
? INDIGO
: isActive
? INDIGO
: "rgba(255,255,255,0.07)",
border: `2px solid ${isDone || isActive ? INDIGO_LIGHT : "rgba(255,255,255,0.12)"}`,
color: isDone || isActive ? "#fff" : TEXT_DIM,
boxShadow: isActive ? `0 0 18px ${INDIGO}88` : "none",
}}
>
{isDone ? "✓" : stageNum}
</div>
<span
style={{
fontFamily: FONT,
fontSize: 11,
fontWeight: isActive ? 600 : 400,
color: isActive ? INDIGO_LIGHT : isPending ? TEXT_DIM : TEXT_MUTED,
letterSpacing: "0.04em",
whiteSpace: "nowrap",
}}
>
{label}
</span>
</div>
</React.Fragment>
);
})}
</div>
);
}
// ─── Sub-component: Callout ───────────────────────────────────────────────────
interface CalloutProps {
label: string;
x: number;
y: number;
arrowDir?: "left" | "right" | "up";
opacity: number;
pulse?: boolean;
}
function Callout({ label, x, y, arrowDir = "left", opacity, pulse = false }: CalloutProps) {
const frame = useCurrentFrame();
const pulseScale = pulse
? 1 + 0.04 * Math.sin((frame / 30) * Math.PI * 2)
: 1;
const arrowStyle: React.CSSProperties =
arrowDir === "left"
? { left: -10, top: "50%", transform: "translateY(-50%)", borderRight: "10px solid rgba(99,102,241,0.9)", borderTop: "6px solid transparent", borderBottom: "6px solid transparent" }
: arrowDir === "right"
? { right: -10, top: "50%", transform: "translateY(-50%)", borderLeft: "10px solid rgba(99,102,241,0.9)", borderTop: "6px solid transparent", borderBottom: "6px solid transparent" }
: { top: -10, left: "50%", transform: "translateX(-50%)", borderBottom: "10px solid rgba(99,102,241,0.9)", borderLeft: "6px solid transparent", borderRight: "6px solid transparent" };
return (
<div
style={{
position: "absolute",
left: x,
top: y,
opacity,
transform: `scale(${pulseScale})`,
transformOrigin: "center",
zIndex: 30,
}}
>
<div
style={{
position: "relative",
background: "rgba(99,102,241,0.9)",
backdropFilter: "blur(8px)",
border: "1px solid rgba(129,140,248,0.6)",
borderRadius: 8,
padding: "8px 14px",
fontFamily: FONT,
fontSize: 13,
fontWeight: 600,
color: "#fff",
whiteSpace: "nowrap",
boxShadow: `0 4px 24px ${INDIGO}55`,
}}
>
<div style={{ position: "absolute", width: 0, height: 0, ...arrowStyle }} />
{label}
</div>
</div>
);
}
// ─── Sub-component: PulsingRing ───────────────────────────────────────────────
interface PulsingRingProps {
x: number;
y: number;
width: number;
height: number;
opacity: number;
}
function PulsingRing({ x, y, width, height, opacity }: PulsingRingProps) {
const frame = useCurrentFrame();
const scale = 1 + 0.06 * Math.abs(Math.sin((frame / 20) * Math.PI));
const ringOpacity = 0.7 + 0.3 * Math.abs(Math.sin((frame / 20) * Math.PI));
return (
<div
style={{
position: "absolute",
left: x - 6,
top: y - 6,
width: width + 12,
height: height + 12,
borderRadius: 12,
border: `2px solid ${INDIGO_LIGHT}`,
opacity: opacity * ringOpacity,
transform: `scale(${scale})`,
transformOrigin: "center",
boxShadow: `0 0 20px ${INDIGO}88`,
zIndex: 20,
pointerEvents: "none",
}}
/>
);
}
// ─── Stage 1: Review Cart ────────────────────────────────────────────────────
function StageReviewCart() {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const panelSpring = spring({ frame, fps, config: { mass: 0.6, stiffness: 100, damping: 16 } });
const panelY = (1 - panelSpring) * 40;
const panelOpacity = interpolate(frame, [0, 10], [0, 1], { extrapolateRight: "clamp" });
const row1 = useSlideUp(8, 20);
const row2 = useSlideUp(16, 20);
const total = useSlideUp(24, 20);
const btn = useSlideUp(32, 20);
const callout = useFadeIn(38);
return (
<div
style={{
position: "absolute",
left: "50%",
top: "50%",
transform: `translate(-50%, calc(-50% + ${panelY}px))`,
opacity: panelOpacity,
width: 520,
}}
>
{/* Panel */}
<div
style={{
background: SURFACE,
border: `1px solid ${BORDER}`,
borderRadius: 16,
padding: 28,
boxShadow: `0 24px 64px rgba(0,0,0,0.5)`,
}}
>
{/* Header */}
<div style={{ marginBottom: 20, display: "flex", alignItems: "center", gap: 10 }}>
<div style={{ width: 8, height: 8, borderRadius: "50%", background: INDIGO }} />
<span style={{ fontFamily: FONT, fontSize: 14, fontWeight: 700, color: TEXT, letterSpacing: "0.06em" }}>
YOUR CART
</span>
<span
style={{
marginLeft: "auto",
fontFamily: FONT,
fontSize: 12,
color: TEXT_MUTED,
background: SURFACE2,
border: `1px solid ${BORDER}`,
borderRadius: 20,
padding: "3px 10px",
}}
>
2 items
</span>
</div>
{/* Divider */}
<div style={{ height: 1, background: "rgba(255,255,255,0.06)", marginBottom: 16 }} />
{/* Cart row 1 */}
<div
style={{
display: "flex",
alignItems: "center",
gap: 14,
marginBottom: 12,
opacity: row1.opacity,
transform: `translateY(${row1.translateY}px)`,
}}
>
<div
style={{
width: 48,
height: 48,
borderRadius: 10,
background: `linear-gradient(135deg, ${INDIGO_DIM}, ${SURFACE2})`,
border: `1px solid ${BORDER}`,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 20,
}}
>
🎧
</div>
<div style={{ flex: 1 }}>
<div style={{ fontFamily: FONT, fontSize: 14, fontWeight: 600, color: TEXT, marginBottom: 2 }}>
Studio Headphones Pro
</div>
<div style={{ fontFamily: FONT, fontSize: 12, color: TEXT_MUTED }}>Midnight Black · Qty 1</div>
</div>
<div style={{ fontFamily: FONT, fontSize: 15, fontWeight: 700, color: TEXT }}>$249.00</div>
</div>
{/* Cart row 2 */}
<div
style={{
display: "flex",
alignItems: "center",
gap: 14,
marginBottom: 20,
opacity: row2.opacity,
transform: `translateY(${row2.translateY}px)`,
}}
>
<div
style={{
width: 48,
height: 48,
borderRadius: 10,
background: `linear-gradient(135deg, #1e3a5f, ${SURFACE2})`,
border: `1px solid rgba(99,102,241,0.12)`,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 20,
}}
>
💻
</div>
<div style={{ flex: 1 }}>
<div style={{ fontFamily: FONT, fontSize: 14, fontWeight: 600, color: TEXT, marginBottom: 2 }}>
USB-C Hub 8-in-1
</div>
<div style={{ fontFamily: FONT, fontSize: 12, color: TEXT_MUTED }}>Space Grey · Qty 1</div>
</div>
<div style={{ fontFamily: FONT, fontSize: 15, fontWeight: 700, color: TEXT }}>$79.00</div>
</div>
{/* Divider */}
<div style={{ height: 1, background: "rgba(255,255,255,0.06)", marginBottom: 16 }} />
{/* Total */}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
opacity: total.opacity,
transform: `translateY(${total.translateY}px)`,
}}
>
<span style={{ fontFamily: FONT, fontSize: 13, color: TEXT_MUTED }}>Order total</span>
<span style={{ fontFamily: FONT, fontSize: 20, fontWeight: 800, color: TEXT }}>$328.00</span>
</div>
{/* Button */}
<div
style={{
position: "relative",
opacity: btn.opacity,
transform: `translateY(${btn.translateY}px)`,
}}
>
<div
style={{
background: `linear-gradient(135deg, ${INDIGO}, #7c3aed)`,
borderRadius: 10,
padding: "14px 24px",
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 8,
fontFamily: FONT,
fontSize: 15,
fontWeight: 700,
color: "#fff",
cursor: "pointer",
boxShadow: `0 8px 32px ${INDIGO}44`,
}}
>
Checkout →
</div>
{/* Pulsing ring on button */}
<PulsingRing x={0} y={0} width={520 - 56} height={50} opacity={callout} />
</div>
</div>
{/* Callout */}
<Callout
label="Click to proceed"
x={-140}
y={355}
arrowDir="right"
opacity={callout}
pulse
/>
</div>
);
}
// ─── Stage 2: Shipping Info ───────────────────────────────────────────────────
function StageShippingInfo() {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const panelSpring = spring({ frame, fps, config: { mass: 0.6, stiffness: 100, damping: 16 } });
const panelY = (1 - panelSpring) * 40;
const panelOpacity = interpolate(frame, [0, 10], [0, 1], { extrapolateRight: "clamp" });
const field1 = useSlideUp(6, 18);
const field2 = useSlideUp(14, 18);
const field3 = useSlideUp(22, 18);
const row = useSlideUp(30, 18);
const callout = useFadeIn(36);
const fieldStyle = (active = false): React.CSSProperties => ({
background: active ? `rgba(99,102,241,0.08)` : SURFACE2,
border: `1.5px solid ${active ? INDIGO : "rgba(255,255,255,0.08)"}`,
borderRadius: 8,
padding: "12px 14px",
fontFamily: FONT,
fontSize: 14,
color: active ? TEXT : TEXT_MUTED,
boxShadow: active ? `0 0 0 3px ${INDIGO}22` : "none",
});
const labelStyle: React.CSSProperties = {
fontFamily: FONT,
fontSize: 11,
fontWeight: 600,
color: TEXT_DIM,
letterSpacing: "0.08em",
marginBottom: 6,
};
return (
<div
style={{
position: "absolute",
left: "50%",
top: "50%",
transform: `translate(-50%, calc(-50% + ${panelY}px))`,
opacity: panelOpacity,
width: 520,
}}
>
<div
style={{
background: SURFACE,
border: `1px solid ${BORDER}`,
borderRadius: 16,
padding: 28,
boxShadow: "0 24px 64px rgba(0,0,0,0.5)",
}}
>
<div style={{ marginBottom: 20, display: "flex", alignItems: "center", gap: 10 }}>
<div style={{ width: 8, height: 8, borderRadius: "50%", background: INDIGO }} />
<span style={{ fontFamily: FONT, fontSize: 14, fontWeight: 700, color: TEXT, letterSpacing: "0.06em" }}>
SHIPPING INFORMATION
</span>
</div>
<div style={{ height: 1, background: "rgba(255,255,255,0.06)", marginBottom: 20 }} />
{/* Full name */}
<div style={{ marginBottom: 16, opacity: field1.opacity, transform: `translateY(${field1.translateY}px)` }}>
<div style={labelStyle}>FULL NAME</div>
<div style={fieldStyle(false)}>Alex Morgan</div>
</div>
{/* Address — highlighted */}
<div style={{ position: "relative", marginBottom: 16, opacity: field2.opacity, transform: `translateY(${field2.translateY}px)` }}>
<div style={labelStyle}>STREET ADDRESS</div>
<div style={fieldStyle(true)}>
<span style={{ color: TEXT_MUTED }}>e.g. 742 Evergreen Terrace…</span>
</div>
<PulsingRing x={0} y={24} width={520 - 56} height={46} opacity={callout} />
</div>
{/* City / Zip */}
<div
style={{
display: "flex",
gap: 12,
opacity: field3.opacity,
transform: `translateY(${field3.translateY}px)`,
}}
>
<div style={{ flex: 2 }}>
<div style={labelStyle}>CITY</div>
<div style={fieldStyle(false)}>Springfield</div>
</div>
<div style={{ flex: 1 }}>
<div style={labelStyle}>ZIP CODE</div>
<div style={fieldStyle(false)}>62704</div>
</div>
</div>
<div
style={{
display: "flex",
gap: 12,
marginTop: 16,
opacity: row.opacity,
transform: `translateY(${row.translateY}px)`,
}}
>
<div
style={{
flex: 1,
background: SURFACE2,
border: "1.5px solid rgba(255,255,255,0.08)",
borderRadius: 10,
padding: "13px 20px",
fontFamily: FONT,
fontSize: 14,
fontWeight: 500,
color: TEXT_MUTED,
textAlign: "center",
}}
>
← Back
</div>
<div
style={{
flex: 2,
background: `linear-gradient(135deg, ${INDIGO}, #7c3aed)`,
borderRadius: 10,
padding: "13px 20px",
fontFamily: FONT,
fontSize: 14,
fontWeight: 700,
color: "#fff",
textAlign: "center",
boxShadow: `0 6px 24px ${INDIGO}44`,
}}
>
Continue to Payment →
</div>
</div>
</div>
{/* Callout */}
<Callout
label="Fill in address"
x={-130}
y={195}
arrowDir="right"
opacity={callout}
pulse
/>
</div>
);
}
// ─── Stage 3: Payment ─────────────────────────────────────────────────────────
function StagePayment() {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const panelSpring = spring({ frame, fps, config: { mass: 0.6, stiffness: 100, damping: 16 } });
const panelY = (1 - panelSpring) * 40;
const panelOpacity = interpolate(frame, [0, 10], [0, 1], { extrapolateRight: "clamp" });
const cardField = useSlideUp(6, 18);
const row1 = useSlideUp(14, 18);
const row2 = useSlideUp(22, 18);
const btns = useSlideUp(30, 18);
const callout = useFadeIn(36);
const labelStyle: React.CSSProperties = {
fontFamily: FONT,
fontSize: 11,
fontWeight: 600,
color: TEXT_DIM,
letterSpacing: "0.08em",
marginBottom: 6,
};
const fieldStyle = (active = false): React.CSSProperties => ({
background: active ? `rgba(99,102,241,0.08)` : SURFACE2,
border: `1.5px solid ${active ? INDIGO : "rgba(255,255,255,0.08)"}`,
borderRadius: 8,
padding: "12px 14px",
fontFamily: FONT,
fontSize: 14,
color: active ? TEXT : TEXT_MUTED,
boxShadow: active ? `0 0 0 3px ${INDIGO}22` : "none",
letterSpacing: active ? "0.18em" : "normal",
});
return (
<div
style={{
position: "absolute",
left: "50%",
top: "50%",
transform: `translate(-50%, calc(-50% + ${panelY}px))`,
opacity: panelOpacity,
width: 520,
}}
>
<div
style={{
background: SURFACE,
border: `1px solid ${BORDER}`,
borderRadius: 16,
padding: 28,
boxShadow: "0 24px 64px rgba(0,0,0,0.5)",
}}
>
<div style={{ marginBottom: 20, display: "flex", alignItems: "center", gap: 10 }}>
<div style={{ width: 8, height: 8, borderRadius: "50%", background: INDIGO }} />
<span style={{ fontFamily: FONT, fontSize: 14, fontWeight: 700, color: TEXT, letterSpacing: "0.06em" }}>
PAYMENT DETAILS
</span>
{/* Card brand icons (mock) */}
<div style={{ marginLeft: "auto", display: "flex", gap: 6 }}>
{["VISA", "MC", "AMEX"].map((b) => (
<div
key={b}
style={{
background: SURFACE2,
border: "1px solid rgba(255,255,255,0.08)",
borderRadius: 4,
padding: "2px 6px",
fontFamily: FONT,
fontSize: 9,
fontWeight: 800,
color: TEXT_DIM,
letterSpacing: "0.05em",
}}
>
{b}
</div>
))}
</div>
</div>
<div style={{ height: 1, background: "rgba(255,255,255,0.06)", marginBottom: 20 }} />
{/* Card number — highlighted */}
<div
style={{
position: "relative",
marginBottom: 16,
opacity: cardField.opacity,
transform: `translateY(${cardField.translateY}px)`,
}}
>
<div style={labelStyle}>CARD NUMBER</div>
<div style={{ ...fieldStyle(true), display: "flex", alignItems: "center", gap: 8 }}>
<span>•••• •••• •••• </span>
<span style={{ color: INDIGO_LIGHT }}>4291</span>
<span style={{ marginLeft: "auto", fontSize: 18 }}>💳</span>
</div>
<PulsingRing x={0} y={24} width={520 - 56} height={46} opacity={callout} />
</div>
{/* Expiry + CVV */}
<div
style={{
display: "flex",
gap: 12,
marginBottom: 16,
opacity: row1.opacity,
transform: `translateY(${row1.translateY}px)`,
}}
>
<div style={{ flex: 1 }}>
<div style={labelStyle}>EXPIRY DATE</div>
<div style={fieldStyle(false)}>08 / 27</div>
</div>
<div style={{ flex: 1 }}>
<div style={labelStyle}>CVV</div>
<div style={fieldStyle(false)}>•••</div>
</div>
</div>
{/* Cardholder */}
<div
style={{
marginBottom: 20,
opacity: row2.opacity,
transform: `translateY(${row2.translateY}px)`,
}}
>
<div style={labelStyle}>CARDHOLDER NAME</div>
<div style={fieldStyle(false)}>ALEX MORGAN</div>
</div>
{/* Buttons */}
<div
style={{
display: "flex",
gap: 12,
opacity: btns.opacity,
transform: `translateY(${btns.translateY}px)`,
}}
>
<div
style={{
flex: 1,
background: SURFACE2,
border: "1.5px solid rgba(255,255,255,0.08)",
borderRadius: 10,
padding: "13px 20px",
fontFamily: FONT,
fontSize: 14,
fontWeight: 500,
color: TEXT_MUTED,
textAlign: "center",
}}
>
← Back
</div>
<div
style={{
flex: 2,
background: `linear-gradient(135deg, ${INDIGO}, #7c3aed)`,
borderRadius: 10,
padding: "13px 20px",
fontFamily: FONT,
fontSize: 14,
fontWeight: 700,
color: "#fff",
textAlign: "center",
boxShadow: `0 6px 24px ${INDIGO}44`,
}}
>
Place Order →
</div>
</div>
</div>
{/* Callout */}
<Callout
label="Enter card details"
x={-136}
y={163}
arrowDir="right"
opacity={callout}
pulse
/>
</div>
);
}
// ─── Stage 4: Confirmation ────────────────────────────────────────────────────
function StageConfirmation() {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const checkSpring = spring({ frame: frame - 8, fps, config: { mass: 0.4, stiffness: 80, damping: 12 } });
const panelOpacity = interpolate(frame, [0, 14], [0, 1], { extrapolateRight: "clamp" });
const titleFade = useFadeIn(20);
const orderFade = useFadeIn(30);
const detailsFade = useFadeIn(40);
const btnFade = useFadeIn(50);
return (
<div
style={{
position: "absolute",
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
opacity: panelOpacity,
width: 480,
textAlign: "center",
}}
>
{/* Checkmark circle */}
<div
style={{
width: 100,
height: 100,
borderRadius: "50%",
background: `linear-gradient(135deg, ${GREEN_DIM}, #166534)`,
border: `3px solid ${GREEN}`,
display: "flex",
alignItems: "center",
justifyContent: "center",
margin: "0 auto 28px",
transform: `scale(${checkSpring})`,
boxShadow: `0 0 48px ${GREEN}55`,
}}
>
<span style={{ fontSize: 44, lineHeight: 1 }}>✓</span>
</div>
{/* Title */}
<div style={{ opacity: titleFade }}>
<div
style={{
fontFamily: FONT,
fontSize: 32,
fontWeight: 800,
color: GREEN,
marginBottom: 8,
textShadow: `0 0 32px ${GREEN}66`,
}}
>
Order Confirmed!
</div>
</div>
{/* Order number */}
<div style={{ opacity: orderFade }}>
<div
style={{
fontFamily: FONT,
fontSize: 18,
fontWeight: 600,
color: TEXT,
marginBottom: 24,
}}
>
Order <span style={{ color: INDIGO_LIGHT }}>#48291</span> confirmed!
</div>
</div>
{/* Details card */}
<div style={{ opacity: detailsFade }}>
<div
style={{
background: SURFACE,
border: `1px solid rgba(34,197,94,0.2)`,
borderRadius: 14,
padding: "20px 24px",
marginBottom: 20,
textAlign: "left",
}}
>
{[
{ label: "Items", value: "Studio Headphones Pro, USB-C Hub" },
{ label: "Total charged", value: "$328.00" },
{ label: "Shipping to", value: "Alex Morgan, Springfield" },
{ label: "Estimated delivery", value: "3–5 business days" },
].map(({ label, value }) => (
<div
key={label}
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 12,
}}
>
<span style={{ fontFamily: FONT, fontSize: 13, color: TEXT_DIM }}>{label}</span>
<span style={{ fontFamily: FONT, fontSize: 13, fontWeight: 600, color: TEXT_MUTED, maxWidth: 240, textAlign: "right" }}>
{value}
</span>
</div>
))}
</div>
</div>
{/* CTA */}
<div style={{ opacity: btnFade }}>
<div
style={{
background: `linear-gradient(135deg, ${GREEN_DIM}, #166534)`,
border: `1.5px solid ${GREEN}`,
borderRadius: 10,
padding: "14px 32px",
fontFamily: FONT,
fontSize: 14,
fontWeight: 700,
color: "#fff",
display: "inline-block",
boxShadow: `0 6px 24px ${GREEN}33`,
}}
>
View Order Details →
</div>
</div>
</div>
);
}
// ─── Main composition ─────────────────────────────────────────────────────────
function WalkthroughCheckout() {
const frame = useCurrentFrame();
// Derive current stage (1-indexed) from global frame
const currentStage =
frame < 60 ? 1 : frame < 120 ? 2 : frame < 180 ? 3 : 4;
// Stage indicator fades in immediately
const indicatorOpacity = interpolate(frame, [0, 12], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ background: BG, overflow: "hidden" }}>
{/* Grid + glow */}
<GridOverlay />
<RadialGlow color={INDIGO} cx="50%" cy="55%" r="60%" />
{/* Stage indicator — persists across all stages */}
<StageIndicator currentStage={currentStage} opacity={indicatorOpacity} />
{/* Stage title strip */}
<div
style={{
position: "absolute",
bottom: 32,
left: "50%",
transform: "translateX(-50%)",
fontFamily: FONT,
fontSize: 12,
fontWeight: 500,
color: TEXT_DIM,
letterSpacing: "0.08em",
}}
>
CHECKOUT FLOW WALKTHROUGH · REMOTION
</div>
{/* Sequences */}
<Sequence from={0} durationInFrames={60}>
<StageReviewCart />
</Sequence>
<Sequence from={60} durationInFrames={60}>
<StageShippingInfo />
</Sequence>
<Sequence from={120} durationInFrames={60}>
<StagePayment />
</Sequence>
<Sequence from={180} durationInFrames={60}>
<StageConfirmation />
</Sequence>
</AbsoluteFill>
);
}
// ─── RemotionRoot ─────────────────────────────────────────────────────────────
export function RemotionRoot() {
return (
<Composition
id="WalkthroughCheckout"
component={WalkthroughCheckout}
durationInFrames={240}
fps={30}
width={1280}
height={720}
/>
);
}
export { WalkthroughCheckout };UI Walkthrough Video
A UI walkthrough that shows four stages of a checkout flow: Cart Review, Shipping Info, Payment, Confirmation. Each stage: a mocked UI panel appears (inline Remotion div-based mockup), callout arrows animate to highlight key elements. Stage indicator at top shows 1/2/3/4 with connecting line. Each stage runs for ~60 frames.
Composition specs
| Property | Value |
|---|---|
| Resolution | 1280 × 720 |
| FPS | 30 |
| Duration | 8 s (240 frames) |
Usage
Copy react.tsx into your Remotion project, import RemotionRoot in your Root.tsx, and run npx remotion studio to preview.
Illustrative animation only — fictional data and content.