R RenderComp
remotion hooks useCurrentFrame useVideoConfig react tutorial

useCurrentFrame and useVideoConfig in Remotion: Complete Guide

useCurrentFrame and useVideoConfig in Remotion: Complete Guide

Every animated element in a Remotion video ultimately traces back to two hooks: useCurrentFrame and useVideoConfig. They are the foundation upon which every fade, slide, counter, and transition is built. Understanding them deeply means understanding how Remotion thinks about time — and once that mental model clicks, every other pattern in the library falls into place naturally.

This guide covers both hooks end-to-end: what they return, why they work the way they do, and how to combine them to write animations that scale correctly regardless of frame rate or composition size.


The Mental Model: Your Video Is a Function of Time

Before touching either hook, it helps to grasp Remotion’s core idea: a video is just a function that maps a frame number to a React render.

f(frame) → React tree → PNG → one frame of video

Remotion’s renderer calls your component once per frame, passing the current frame number through React context. Your component reads that number, computes its visual state at that moment, and returns JSX. The renderer captures the result as a PNG. Repeat for every frame in the composition, stitch the images together, and you have a video.

This is profoundly different from how timeline-based tools work. There are no keyframe curves to drag. There is no notion of “tweening” that happens automatically. The entire animation is just arithmetic: given this frame number, what should be on screen?

useCurrentFrame and useVideoConfig are the two hooks that feed your components the inputs they need to do that arithmetic.


useCurrentFrame() — What It Returns

import { useCurrentFrame } from "remotion";

const frame = useCurrentFrame();

useCurrentFrame() returns a single integer: the current frame number, zero-indexed. The first frame of your composition is 0. If your composition is 150 frames long, the last frame is 149.

A few important properties:

  • It is always an integer. Remotion renders discrete frames; there is no fractional frame.
  • It is relative to the nearest enclosing <Sequence>. If you wrap a component in <Sequence from={30}>, useCurrentFrame() inside that component returns 0 when the global playhead is at frame 30 — not 30. This lets you build self-contained sub-animations without needing to offset every calculation manually.
  • It is injected via React context. Do not pass frame as a prop between components — just call useCurrentFrame() wherever you need the current time.

When to use it

Use useCurrentFrame() anytime you want a value to change over time. Opacity? Compute it from the frame. Position? Frame. Scale, color, rotation, blur radius — all frame.


Building a Fade-In Animation from Scratch

A fade-in is the simplest animation: opacity goes from 0 to 1 over some number of frames. Here is how to build one using only useCurrentFrame and interpolate.

import { useCurrentFrame, interpolate } from "remotion";

export const FadeIn: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const frame = useCurrentFrame();

  const opacity = interpolate(frame, [0, 30], [0, 1], {
    extrapolateRight: "clamp",
  });

  return <div style={{ opacity }}>{children}</div>;
};

interpolate is Remotion’s mapping utility. It takes an input value (the frame), an input range ([0, 30] — frames 0 through 30), and an output range ([0, 1] — opacity 0 through 1). The extrapolateRight: "clamp" option tells interpolate to stop at 1 once the frame exceeds 30, rather than continuing upward past full opacity.

This is the basic pattern for nearly every property animation in Remotion:

  1. Read frame from useCurrentFrame().
  2. Pass it through interpolate() to map a frame range to a value range.
  3. Apply the result to a style or prop.

useVideoConfig() — Composition Metadata

import { useVideoConfig } from "remotion";

const { width, height, fps, durationInFrames } = useVideoConfig();

useVideoConfig() returns metadata about the current composition:

PropertyTypeDescription
widthnumberComposition width in pixels
heightnumberComposition height in pixels
fpsnumberFrames per second
durationInFramesnumberTotal frame count of the composition (or enclosing <Sequence>)

There is also a id property (the composition’s string identifier) and defaultProps / props for data-driven compositions, but fps, durationInFrames, width, and height are the four you will reach for constantly.

Why this matters

Hard-coding numbers is the fastest path to broken animations. Suppose you build a composition at 30 fps and hard-code 60 as the “two second” frame count. When the composition is exported at 60 fps, the animation plays in one second instead of two. When the canvas is resized from 1920×1080 to 1080×1080, elements overflow or misalign.

useVideoConfig() eliminates both classes of bugs. It is the single source of truth for your composition’s parameters.


Responsive Animations Using Both Hooks Together

The real power emerges when you combine useCurrentFrame and useVideoConfig. Here is a progress bar that always fills over exactly the first two seconds, regardless of frame rate:

import { useCurrentFrame, useVideoConfig, interpolate } from "remotion";

export const ProgressBar: React.FC = () => {
  const frame = useCurrentFrame();
  const { fps, width } = useVideoConfig();

  const twoSeconds = 2 * fps;

  const barWidth = interpolate(frame, [0, twoSeconds], [0, width], {
    extrapolateRight: "clamp",
  });

  return (
    <div style={{ width, height: 8, background: "#222" }}>
      <div style={{ width: barWidth, height: 8, background: "#4ade80" }} />
    </div>
  );
};

Two things to notice:

  1. twoSeconds = 2 * fps converts a human-readable duration to frames dynamically. At 30 fps this is 60; at 60 fps it is 120. The animation always takes exactly two seconds.
  2. The bar’s maximum width is width from useVideoConfig, so it fills the composition exactly — whether you are targeting 1920px or 1080px.

Frame-to-Second Conversion and Timing Math

The conversion between frames and seconds is a constant source of confusion for developers new to Remotion. The rule is simple:

seconds = frame / fps
frames  = seconds * fps

A few common calculations:

const { fps, durationInFrames } = useVideoConfig();
const frame = useCurrentFrame();

// Current time in seconds
const currentSeconds = frame / fps;

// Total duration in seconds
const totalSeconds = durationInFrames / fps;

// Progress as a 0-to-1 value
const progress = frame / durationInFrames;

// Start an animation at exactly 1.5 seconds
const delayedFrame = Math.max(0, frame - Math.round(1.5 * fps));

The delayedFrame pattern is particularly useful: by subtracting a delay from frame and clamping at zero, you shift an animation’s start point in time without restructuring your component tree. Combine it with interpolate’s extrapolateLeft: "clamp" option and the element simply holds its initial state until the delay has elapsed.


Common Patterns

Entrance animations

Most entrance animations follow the same three-step structure: hold the initial state, animate to the final state over a short window, then hold the final state.

const frame = useCurrentFrame();
const { fps } = useVideoConfig();

// Slide in over 20 frames starting at frame 0
const translateY = interpolate(frame, [0, 20], [40, 0], {
  extrapolateLeft: "clamp",
  extrapolateRight: "clamp",
});

Countdowns

A countdown timer reads the total duration, subtracts elapsed frames, and displays the remaining seconds:

const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();

const remainingFrames = durationInFrames - frame;
const remainingSeconds = Math.ceil(remainingFrames / fps);

Math.ceil ensures the display shows 1 for the full last second rather than flickering between 1 and 0.

Syncing to audio cue points

When you know a beat or sound effect lands at a specific second, convert it to a frame for the animation trigger:

const { fps } = useVideoConfig();
const frame = useCurrentFrame();

const beatFrame = Math.round(2.4 * fps); // beat at 2.4 seconds

const scale = interpolate(frame, [beatFrame, beatFrame + 6], [1, 1.15], {
  extrapolateLeft: "clamp",
  extrapolateRight: "clamp",
});

This keeps the animation locked to the audio cue regardless of frame rate.


Performance: Rules of Hooks in Remotion

Remotion follows all standard React hooks rules — no conditional calls, no loops, no nested calls inside callbacks. But there are a few Remotion-specific behaviors worth knowing:

Do not drive animations with CSS transitions or CSS animations. Remotion renders each frame in isolation. A CSS transition: opacity 0.3s does nothing useful in a rendered video because there is no persistent browser state between frames. Every animated value must be deterministically computable from the current frame number alone.

useCurrentFrame() is the only source of time. Do not use Date.now(), performance.now(), setTimeout, setInterval, or any other time source. These are non-deterministic. Two renders of the same frame could produce different results, breaking Remotion’s ability to render frames in parallel or out of order.

Avoid side effects inside render. React’s render functions should be pure. useEffect and useLayoutEffect do not behave as expected in Remotion’s rendering pipeline. Anything that needs to happen “over time” should be expressed as a function of the current frame, not as a side effect.

useVideoConfig() is cheap. It reads from context; there is no performance cost to calling it in many components. Use it freely whenever you need composition metadata rather than threading props down.


FAQ

Q: Can I call useCurrentFrame() in multiple components?

Yes. Call it in every component that needs to know the current time. Each call reads from the same React context value, so all components see the same frame number within a single render pass.

Q: Does useCurrentFrame() return a float or an integer?

It always returns an integer. Remotion renders discrete frames; there is no sub-frame precision.

Q: What happens if I use useCurrentFrame() outside a Remotion composition?

Remotion will throw an error explaining that the hook must be called within a composition rendered by Remotion’s renderer. You cannot use these hooks in a regular React app unless it is wrapped in Remotion’s context providers (which the <Player> component handles for you).

Q: How do I animate something relative to the end of the composition rather than the start?

Use durationInFrames from useVideoConfig() and subtract:

const { durationInFrames } = useVideoConfig();
const frame = useCurrentFrame();
const frameFromEnd = durationInFrames - frame;

Then use frameFromEnd as your input to interpolate.

Q: Why does my animation stop working when I nest it inside a <Sequence>?

Inside a <Sequence from={N}>, useCurrentFrame() returns frames relative to the sequence’s start (starting at 0), not the global composition time. This is intentional — it makes sub-components composable and reusable. If you need the global frame inside a sequence, read the frame, then add the sequence’s offset explicitly. In practice, it is usually better to design components to be offset-agnostic.

Q: Is there an equivalent of useCurrentFrame in Remotion’s <Player> component for web embeds?

When embedding a Remotion composition in a web page via <Player>, you can use the useCurrentPlayerFrame hook (from @remotion/player) inside the embedded composition to sync playback-driven animations. The core useCurrentFrame hook continues to work as normal inside compositions rendered by <Player>.

Q: Can I pass frame down as a prop instead of calling useCurrentFrame() in each child?

You can, but it is considered an anti-pattern. It creates prop-drilling and couples your component tree to the timing model. The idiomatic approach is to call useCurrentFrame() directly in any component that needs it. The context lookup is extremely fast.


Putting It All Together

Here is a self-contained example that uses both hooks to create a title card that fades in over the first half second, stays visible, then fades out over the last half second:

import { useCurrentFrame, useVideoConfig, interpolate } from "remotion";

export const TitleCard: React.FC<{ title: string }> = ({ title }) => {
  const frame = useCurrentFrame();
  const { fps, durationInFrames, width, height } = useVideoConfig();

  const fadeInEnd = Math.round(0.5 * fps);
  const fadeOutStart = durationInFrames - Math.round(0.5 * fps);

  const opacity = interpolate(
    frame,
    [0, fadeInEnd, fadeOutStart, durationInFrames],
    [0, 1, 1, 0],
    { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
  );

  return (
    <div
      style={{
        width,
        height,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        opacity,
        background: "#0f172a",
      }}
    >
      <h1 style={{ color: "#f8fafc", fontSize: 72, fontFamily: "sans-serif" }}>
        {title}
      </h1>
    </div>
  );
};

This component is entirely self-contained. Change the composition’s fps or durationInFrames and the fade durations scale proportionally. Resize the canvas and the layout fills it correctly. No magic numbers.


Skip the Boilerplate — Use RenderComp Templates

Building these patterns from scratch is a great way to learn how Remotion works. Once you understand the fundamentals, though, hand-writing entrance animations, progress bars, countdown timers, and title cards for every new project adds up quickly.

RenderComp templates are production-ready Remotion compositions built on exactly the patterns described in this guide. Each template is fully parameterized — pass your content as props and it handles the timing math internally. Browse the template library and skip straight to the creative work.

Use RenderComp templates to skip the boilerplate → rendercomp.com

Now available

Get 1,000+ Remotion Templates

Pay once — no subscription. Lifetime updates. TypeScript-first.

View pricing →