Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Structure Remotion markup for interactivity
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 57% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 59% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 68% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 93% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 42% | 0% |
By writing Remotion markup in a specific way, the Remotion Studio is able to recognize the structure of the code and makes it interactive:
If the markup is too complex for the Studio to make it interactive, then the values become grayed out.
InteractiveEvery HTML and SVG element (except <Img>, it already is interactive) such as <div> can be turned interactive using Interactive:
tsx title="Interactive elements"<Interactive.Div name="Greeting card" style={{fontSize: 80, padding: 24}} > Hello </Interactive.Div>
This allows styles and keyframes to be set in the Studio. Be sensible, if a component has many elements, the timeline might get messy.
If text is fixed and only used once, write it directly inside the interactive element instead of extracting it into a constant.
tsx title="Inline text"// 👍 Fixed copy stays editable <Interactive.Div name="Title"> Remotion Best Practices </Interactive.Div>
Use a prop or variable only when the text is dynamic or reused.
Add a name prop to elements to make them easily identifyable. Avoid computed names, hardcode them.
tsx title="Interactive names"<> <Interactive.Div name="Hero title" style={{fontSize: 80}}> Launch day </Interactive.Div> <Img name="Avatar" src="https://remotion.media/image.jpeg" /> <Video name="Background" src="https://remotion.media/video.mp4" /> <Sequence name="Title"> Launch day </Sequence> </>
The best way is to just pass a plain object to style - no referring to constants, no object spreading, no math.
tsx title="Interactive example"<Interactive.Div style={{ fontSize: 80, color: 'red', }} > Hello World! </Interactive.Div>
tsx title="❌ Bad for interactivity"const baseStyle = useMemo(() => { return { fontSize: 12 // ❌ Non-inline styles are not supported } }, []); <Interactive.Div style={{ ...baseStyle, // ❌ Spreading is not supported color: RED, // ❌ Referring to constants is not supported scale: frame * 10 // ❌ Math is not supported }} > Hello World! </Interactive.Div>
interpolate()Write animations as inline interpolate() calls on the property that changes. The output range, easing, extrapolation and output property should use hardcoded values.
The input range may additionally use durationInFrames, fps, width and height destructured directly from useVideoConfig(). Bare identifiers such as durationInFrames, multiplication with a number such as 2 * fps or fps * 2, and subtraction of a number such as durationInFrames - 1 are supported.
tsx title="Inline values"const {fps, durationInFrames} = useVideoConfig(); // 👍 Inline values can be standardized and keyframed <Interactive.Div name="Product card" style={{ color: 'white', fontSize: 80, scale: interpolate(frame, [0, fps], [0, 1], { easing: Easing.spring({damping: 200}), output: 'perceptual-scale', extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }), rotate: interpolate(frame, [0, 1 * fps], ['0deg', '20deg'], { easing: Easing.spring({damping: 200}), extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }), translate: interpolate( frame, [durationInFrames - 30, durationInFrames], ['0px 0px', '0px 120px'], { easing: Easing.spring({damping: 200}), output: 'perceptual-scale', extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } ), }} />
tsx title="❌ Bad interactivity"const translateY = interpolate(frame, [0, 30], [0, 120]); // ❌ Math should be directly in the markup <Interactive.Div name="Product card" style={{ translate: translateY, // ❌ Only inline interpolate() calls are supported, rotate: interpolate(frame, [start, start + 10], [0, Math.PI]), // ❌ Cannot use math with arbitrary variables, cannot use constants scale: interpolate(anyVariable, [0, 30], [0, 1]) // ❌ Can only interpret the `frame` variable. }} />
scale, translate, rotate CSS propertiesAvoid the transform CSS property. If possible, use scale, rotate and translate instead because only they are interactively editable.
When scaffolding a composition, keep width, height, fps, durationInFrames and defaultProps inline and make no type assertions.
The Props editor can save visual edits back to your code when defaultProps is an inline object literal on <Composition> or <Still>.
tsx// 👍 Static values are in <Composition>, dynamic values are in calculateMetadata() const calculateMetadata = useMemo(async () => { const dimensions = await getDimensions(); // just an example return {width: dimensions.width, height: dimensions.height}; }); <Composition id="my-video" component={MyComponent} durationInFrames={150} fps={30} calculateMetadata={calculateMetadata} defaultProps={{title: 'Hello', color: '#0b84ff'}} />
tsx title="Negative examples"const defaultProps = {title: 'Hello', color: '#0b84ff'}; // ❌ Don't extract defaultProps, must be inline const calculateMetadata = useMemo(() => { // ❌ Unnecessary because no calculation is being done, return {durationInFrames: 150, fps: 30, width: 1920, height: 1080}; }); <Composition id="my-video" component={MyComponent} calculateMetadata={calculateMetadata} defaultProps={{ title: 'Hello', } as Props} // ❌ Don't have type assertions, instead type MyComponent correctly />
Use only calculateMetadata() for the part of the metadata that is dynamic.
The effects array should not be computed. The same rules for setting keyframes as interpolate() apply too here: All values should also be hardcoded: Input range, output range, easing, extrapolation, output property.
tsx title="Effects"// 👍 Parameters are inline and the array shape is stable <CanvasImage src={src} width={1280} height={720} effects={[ radialProgressiveBlur({ center: [0.5, 0.5], width: 1.2, height: 0.8, start: 0.2, disabled: true, rotation: interpolate(frame, [0, 120], [0, 180]), }), ]} /> const center = [0.5, 0.5] as const; const rotation = frame * 1.5; <CanvasImage src={src} width={1280} height={720} // ❌ Conditional effect is not animateable effects={enabled ? [ radialProgressiveBlur({ // ❌ Not inline center, rotation, }), ] : []} />
Render separate elements if one version should have effects and another should not.
To make a custom userland component interactive, use: Make a component interactive
If a Remotion component mainly consists of video and audio clips, see Video editing for best practices on how to structure Remotion markup so the clips are interactively editable in the timeline.
Other measured skills in the registry, with their headline benchmark lift.