ZyVOP Logo
Content That Connects
SeriesAI NewsCategoriesWrite for Us
ZyVOP Logo
Content That Connects

Empowering developers and creators with cutting-edge insights, comprehensive tutorials, and innovative solutions for the digital future.

Content

  • Tags
  • Badges
  • Write Article
  • Newsletter

Company

  • About Us
  • Write for Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

© 2026 ZyVOP. Crafted with care for the developer community.

Made with ❤️ by the ZyVOP team
All systems operational
HomeHow to Make a Product Demo Video in React

How to Make a Product Demo Video in React

Remocn
RemocnSoftware Engineer
August 1, 2026
9 min read
2 views
How to Make a Product Demo Video in React
#JavaScript#react#tutorial#webdev
👍1

This is a build log for a 30-second product demo video, written entirely in React and rendered to an .mp4 you can drop on a landing page, post to X, or attach to a Product Hunt launch. No After Effects, no screen recorder, no dragging clips around a GUI timeline. The stack is Remotion — the React framework that renders real video from components — and remocn, a shadcn-style registry of copy-paste animation and UI components built for it. We'll assemble three scenes: a hook line over an animated background, a simulated product walkthrough (the app UI rebuilt on a timeline instead of screen-recorded), and a closing scene with a live star counter and a call to action. Every scene is a React component, and the whole thing is one file tree you can re-render whenever your UI changes.

Why build a demo video in code

A GUI editor is faster for a one-off. A demo video is rarely a one-off: the product UI changes, the pricing changes, the headline changes, and every change means re-recording and re-cutting. When the video is React, a change is a prop edit and a re-render, and the diff lives in git next to the feature that caused it. You also skip the screen recorder entirely — instead of capturing your running app, you rebuild the parts you want to show as components, which stay crisp at any resolution and never leak a stray notification or the wrong cursor. The tradeoff is real: you write code instead of dragging clips, so this pays off when the video has a life beyond its first export. (There's a longer editor-versus-code comparison elsewhere; this article is the build.)

Install Remotion and the components

remocn assumes a Remotion project already exists. If you don't have one, create-video scaffolds it; if you've never run the shadcn CLI here, init writes a components.json. Then a single add pulls every component this article uses.

# 1. A Remotion project (skip if you already have one)
npx create-video@latest

# 2. shadcn config (skip if you already have components.json)
npx shadcn@latest init

# 3. Every component this article uses, in one run
npx shadcn@latest add \
  @remocn/shader-mesh-gradient @remocn/staggered-fade-up \
  @remocn/signup-flow @remocn/backdrop \
  @remocn/whip-pan @remocn/zoom-blur \
  @remocn/github-stars @remocn/soft-blur-in @remocn/confetti \
  @remocn/terminal-simulator

Enter fullscreen mode Exit fullscreen mode

remocn follows shadcn's "own your code" model: add copies each component's TypeScript source into your project under components/remocn/, so you import them by path (@/components/remocn/signup-flow) and edit them like your own files — there's no runtime dependency to track. The registry also resolves dependency chains for you. signup-flow alone pulls in the remocn-ui core plus cursor, input, button, toast, field, and blur-in, and whip-pan installs @remotion/transitions, so the single command above leaves you with a working set.

One licensing note, accurate at the time of writing: Remotion is source-available and free for individuals and companies of up to three people; larger companies need a paid company license. remocn itself is MIT.

Set up the composition

A Remotion video is a <Composition>: an id, the root component to render, a frame count, a frame rate, and a resolution. We're rendering 1920×1080 at 30 fps. Thirty seconds is 900 frames, and after accounting for the two transitions the exact number lands at 906 — here's the budget the rest of the article fills in.

Scene

What's on screen

Frames

Seconds

1 — Hook

shader background + StaggeredFadeUp headline

150

5.0

↳ whip-pan

overlaps scenes 1 and 2

−26

−0.9

2 — Product

signup-flow simulated on a timeline

500

16.7

↳ zoom-blur

overlaps scenes 2 and 3

−18

−0.6

3 — Closing

github-stars + CTA + confetti

300

10.0

Total

906

30.2

The scene sequences sum to 950 frames; the two transitions overlap their neighbors by 26 and 18 frames, so the finished video is 950 − 44 = 906 frames. create-video already wired registerRoot for you — you only edit the root:

// src/Root.tsximport { Composition } from "remotion";import { ProductDemo } from "./ProductDemo";

export const RemotionRoot = () => (<Composition
    id="ProductDemo"
    component={ProductDemo}
    durationInFrames={906}
    fps={30}
    width={1920}
    height={1080}/>);

Enter fullscreen mode Exit fullscreen mode

ProductDemo is the component we build up over the next sections. Preview it live at any time with npx remotion studio, where you can scrub the timeline frame by frame.

Scene 1 — the hook line

The opening has one job: state the value in a few words before anyone scrolls past. We put an animated shader behind a headline that assembles word by word. Both are remocn components, and both are deterministic — the shader freezes its own internal clock and instead reads the current frame, so the motion is a pure function of the timeline rather than wall-clock time. That's what makes a Remotion render reproducible across machines and safe to render in parallel chunks.

// src/scenes/HookScene.tsximport { AbsoluteFill } from "remotion";import { ShaderMeshGradient } from "@/components/remocn/shader-mesh-gradient";import { StaggeredFadeUp } from "@/components/remocn/staggered-fade-up";

export const HookScene = () => (<AbsoluteFill>
    <ShaderMeshGradient speed={1} />
    <AbsoluteFill
      style={{ alignItems: "center", justifyContent: "center", padding: 160 }}
    >
      <StaggeredFadeUp
        text="Deploy in one command"
        staggerDelay={4}
        distance={20}
        fontSize={96}
        fontWeight={600}
        color="#0b0b0f"
      />
    </AbsoluteFill></AbsoluteFill>);

Enter fullscreen mode Exit fullscreen mode

ShaderMeshGradient fills the frame; the second AbsoluteFill layers the headline on top. StaggeredFadeUp lifts each word up by distance pixels and fades it in, offsetting each next word by staggerDelay frames — at 4 frames a word, a four-word line finishes assembling in well under a second. Mesh gradients render bright, so the headline uses a near-black color; if you swap in a darker shader (there are 18 in the registry), flip the text to white. The typography components size themselves from fontSize, so they're resolution-independent — no wrapper needed here.

Scene 2 — simulate the product on a timeline

This is the scene most people reach for a screen recorder to make. Don't. Recording your running app captures whatever resolution your display happens to be, drags in real network lag, and risks a notification popping in mid-take. The alternative — the "fake app" technique — is to rebuild the slice of UI you want to show as a component and script it on the timeline. It renders retina-sharp at any output resolution, it re-renders when your real UI changes, and every take is identical.

remocn ships these as scripted flows. signup-flow drives a complete account-creation sequence: a cursor moves field to field, each input fills in, the submit button steps through hover → press → loading → success, and a success toast slides in — all keyed to frame numbers, not timers. You feed it your copy through props.

These scene sims are authored against a 1280×720 reference so their internal pixel math stays predictable. To drop one into a 1080p composition, scale that reference up once with a small wrapper you'll reuse for the closing scene too:

// src/components/Stage.tsximport type { ReactNode } from "react";import { AbsoluteFill } from "remotion";

export const Stage = ({ children }: { children: ReactNode }) => (<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
    <div style={{ width: 1280, height: 720, transform: "scale(1.5)" }}>
      {children}
    </div></AbsoluteFill>);

Enter fullscreen mode Exit fullscreen mode

A 1280×720 box scaled by 1.5 is exactly 1920×1080, centered. Now the scene: a soft gradient behind the scaled signup card.

// src/scenes/ProductScene.tsximport { AbsoluteFill } from "remotion";import { SignupFlow } from "@/components/remocn/signup-flow";import { Stage } from "../components/Stage";

export const ProductScene = () => (<AbsoluteFill style={{ background: "linear-gradient(160deg, #eef2ff, #faf5ff)" }}>
    <Stage>
      <SignupFlow
        title="Create your Rocket account"
        description="Start your 14-day trial — no card required"
        fullName="Ada Lovelace"
        email="ada@rocket.dev"
        password="••••••••"
        createLabel="Create account"
        toastTitle="Welcome aboard"
      />
    </Stage></AbsoluteFill>);

Enter fullscreen mode Exit fullscreen mode

Every string is a prop, so this becomes your product by editing the copy — no re-recording. signup-flow's scripted timeline runs about 360 frames (roughly 12 seconds), which is why Scene 2 gets 500 frames in the budget: enough to play the whole flow and hold on the completed form for a beat before the transition out. If your product is a CLI rather than a web app, swap signup-flow for terminal-simulator, which types commands into a console window and rolls older lines off the top — same idea, different surface.

Stitch the scenes with TransitionSeries

Individual scenes are just components. To cut between them with motion, wrap them in TransitionSeries from @remotion/transitions and drop a TransitionSeries.Transition between each pair. remocn's transitions are presentation functions you hand to that element: whipPan flings both scenes through the frame in a single camera whip, and zoomBlur punches forward into the next scene with a blur. A transition doesn't add time — it overlaps the last frames of the outgoing scene with the first frames of the incoming one, so the finished duration is the sum of the sequences minus the sum of the transitions.

// src/ProductDemo.tsximport {TransitionSeries,linearTiming,springTiming,} from "@remotion/transitions";import { whipPan } from "@/components/remocn/whip-pan";import { zoomBlur } from "@/components/remocn/zoom-blur";import { HookScene } from "./scenes/HookScene";import { ProductScene } from "./scenes/ProductScene";import { ClosingScene } from "./scenes/ClosingScene";

export const ProductDemo = () => (<TransitionSeries>
    <TransitionSeries.Sequence durationInFrames={150}>
      <HookScene />
    </TransitionSeries.Sequence>

    <TransitionSeries.Transition
      timing={linearTiming({ durationInFrames: 26 })}
      presentation={whipPan({ direction: "left", blur: 24 })}
    />

    <TransitionSeries.Sequence durationInFrames={500}>
      <ProductScene />
    </TransitionSeries.Sequence>

    <TransitionSeries.Transition
      timing={springTiming({ durationInFrames: 18, config: { damping: 200 } })}
      presentation={zoomBlur({ blur: 16 })}
    />

    <TransitionSeries.Sequence durationInFrames={300}>
      <ClosingScene />
    </TransitionSeries.Sequence></TransitionSeries>);

Enter fullscreen mode Exit fullscreen mode

The timing and presentation props are separate on purpose: presentation is the look of the move, timing is its curve and length. whipPan reads well on a linear ramp. zoomBlur applies no internal easing of its own, so its character comes entirely from the timing function — springTiming gives the punch-in a springy settle. Sequences sum to 950 frames, the two transitions overlap 44, and the result matches the 906 we set on the <Composition>. ClosingScene is the one piece still missing; it's next.

Scene 3 — social proof and the call to action

The close does two things back to back: show that other people already use the product, then tell the viewer what to do. github-stars handles the first half — a fly-through of stargazers with an odometer that counts up to the total. It renders standalone from built-in sample data, or you pass real stargazers you fetched. Like the signup sim, it targets a 1280×720 reference, so it goes through the same Stage wrapper. Then a Backdrop fills the frame for the CTA: SoftBlurIn resolves the final line out of a blur, and Confetti fires on top.

// src/scenes/ClosingScene.tsximport { AbsoluteFill, Sequence } from "remotion";import { Backdrop } from "@/components/remocn/backdrop";import { Confetti } from "@/components/remocn/confetti";import { GitHubStars } from "@/components/remocn/github-stars";import { SoftBlurIn } from "@/components/remocn/soft-blur-in";import { Stage } from "../components/Stage";

const STARS_LEN = 150;

export const ClosingScene = () => (<AbsoluteFill>
    <Sequence durationInFrames={STARS_LEN}>
      <Stage>
        <GitHubStars
          repo="rocket-dev/rocket"
          totalStars={4218}
          accentColor="#f5a623"
          orientation="horizontal"
          speed={1}
          theme="dark"
        />
      </Stage>
    </Sequence>

    <Sequence from={STARS_LEN}>
      <Backdrop
        fill={{ type: "color", value: "#0b0b0f" }}
        padding={0}
        radius={0}
        shadow=""
      >
        <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
          <SoftBlurIn
            text="Start building with Rocket"
            fontSize={84}
            fontWeight={600}
            color="#fafafa"
          />
        </AbsoluteFill>
        <Confetti startFrame={10} particleCount={160} seed={7} originY={0.4} />
      </Backdrop>
    </Sequence></AbsoluteFill>);

Enter fullscreen mode Exit fullscreen mode

The two halves are ordinary <Sequence> elements inside the scene: the star fly-through owns the first 150 frames, and the CTA owns the rest, starting at from={STARS_LEN}. Backdrop is remocn's background component — with padding, radius, and shadow zeroed out it's a full-bleed fill, and if you leave those defaults on instead it wraps the content in a rounded, shadowed floating-window frame. Confetti's startFrame is counted from the start of its Sequence, so 10 means ten frames after the CTA appears. It's worth calling out why the confetti looks the same on every render: each piece comes from a seeded pseudo-random generator, never Math.random(), so a given seed produces an identical burst every time — which is exactly what a deterministic, parallelized render needs.

Render to mp4

With the root registered, one command turns the composition into a file:

npx remotion render ProductDemo out/demo.mp4

Enter fullscreen mode Exit fullscreen mode

That renders ProductDemo at the resolution and frame count from the <Composition> and writes out/demo.mp4. While you're iterating on a single scene, render just its frames instead of all 906 — npx remotion render ProductDemo out/scene2.mp4 --frames=124-624 gives you the product scene in a fraction of the time (it starts at frame 124, since the 26-frame whip-pan overlap pulls it back from 150). When you want the full render to run somewhere other than your laptop — a CI job, or a render button inside your own app — @remotion/renderer drives the identical render headlessly with selectComposition then renderMedia, and Remotion Lambda splits the frames into parallel chunks in the cloud for speed. Because every component here is a pure function of the frame, all of those paths produce the same pixels.

Where to go next

The full catalog — 18 shaders, the rest of the camera and dissolve transitions, chart and chat sims, and the video-ready UI primitives that power the fake-app flows — lives at remocn.dev/docs. The two most useful next stops for a demo are the transitions you cut with and the UI primitives you compose your own product scenes from. Everything is MIT and on GitHub at github.com/Remocn/remocn — a star helps if this saved you an afternoon. Swap the signup sim for your product's real flow, change the copy, and the same three-scene skeleton becomes your next launch video.

Remocn

Remocn

Software Engineer

Production-ready animations, transitions, backgrounds, and scenes for Remotion

Comments (0)

Login to post a comment.

Related Posts

React Performance Optimization

I joined a large Canadian automotive company as one of three senior frontend engineers. The site was public-facing — a product catalog where users browsed vehic...

Read article

Type-Safe React Architecture

I remember the exact moment I realized I couldn't go back. I had been writing TypeScript for a while, got used to it, and then joined a project that was pure Ja...

Read article

Building Design Systems That Scale

At Motorola Solutions I walked into a codebase with hundreds of internal projects. Every team followed the brand book — in theory. In practice, it was chaos. Bu...

Read article

Starting a New React Project in 2026-2027: The Stack I Keep Coming Back To

I was three weeks into a new client engagement — a mid-sized insurance company building a policy administration CRM from scratch. No legacy code, no inherited d...

Read article

TypeScript in React: Patterns That Actually Matter

Six weeks into the insurance CRM project, we had a production incident. The backend team renamed a field — policy_number became policyNumber — as part of a snak...

Read article