Skip to main content

Interactivity best practices

Write editable Remotion components so the Studio interactivity can understand the values that should be changed visually.

When a value is shown as computed in the Studio, it often means that the value is hidden behind a variable, helper function, conditional expression or transform string.

Quick checklist

When converting a component for Studio interactivity, apply all of these rules together:

  • Use Interactive.* for editable HTML or SVG elements, or Interactive.withSchema() for custom components.
  • Add a descriptive name prop to the editable element or custom component.
  • Keep every editable or keyframed value inline at the JSX prop that the Studio edits.
  • Keep interpolate() keyframe arrays as hardcoded numeric arrays, such as [0, 30].
  • Use Studio-editable style props such as translate, scale, rotate, transformOrigin and opacity instead of hiding values in a transform string.
  • Use translate for animated movement and canvas dragging. Keep top, left, right and bottom for static anchoring.
  • Keep <Composition> metadata and defaultProps inline unless the value is truly dynamic.

Use Interactive.* for tweakable elements

Use the Interactive namespace when an HTML or SVG element should be selected, moved or edited in the Studio.

Interactive elements
// 👍 Selectable and editable in the Studio <Interactive.Div name="Greeting card" style={{fontSize: 80, padding: 24}}> Hello </Interactive.Div> // 👎 A regular element is not Studio-editable <div style={{fontSize: 80, padding: 24}}>Hello</div>

For a custom component, see Make a component interactive.

Prompt
Make this interactive in the Remotion Studio: https://www.remotion.dev/docs/studio/interactivity-best-practices.md

Give interactive elements a descriptive name

Add a name prop to interactive elements and custom interactive components. Use a short label that describes the element in the scene.

The name is shown in the Studio timeline and helps agents identify the correct element when making visual edits.

Interactive names
// 👍 Easy to find in the Studio timeline and by agents <Interactive.Div name="Hero title" style={{fontSize: 80}}> Launch day </Interactive.Div> // 👎 Omitted names are hard to identify in the Studio timeline and by agents <Interactive.Div style={{fontSize: 80}}> Launch day </Interactive.Div>

Keep editable values visible

Put values that should be edited in the Studio directly into the JSX. This applies to static style values, keyframes, easing and transform props.

If the Studio should expose a property such as opacity, translate, scale or rotate, keep the interpolate() call on that exact style property. Do not calculate the value in a local variable and pass the variable into style. Keep the input range and output range directly in that interpolate() call. The input range must use hardcoded numbers, not variables or expressions such as [sendStartFrame, sendStartFrame + duration].

Inline values
// 👍 Inline values can be standardized and keyframed <Interactive.Div name="Product card" style={{ color: 'white', fontSize: 80, scale: interpolate(frame, [0, 30], [0, 1], { easing: Easing.spring({damping: 200}), }), rotate: interpolate(frame, [0, 30], ['0deg', '20deg']), translate: interpolate(frame, [0, 30], ['0px 0px', '0px 120px']), }} /> // 👎 Values hidden behind variables, helpers or strings become computed const container: React.CSSProperties = { color: 'white', fontSize: 80, }; const translateY = interpolate(frame, [0, 30], [0, 120]); const rotation = interpolate(frame, [0, 30], [0, 20]); <Interactive.Div name="Product card" style={{ ...container, transform: `translateY(${translateY}px) rotate(${rotation}deg)`, }} />

Use standalone spring() only when the animation needs a frame-driven physical spring simulation. Use individual CSS transform properties such as scale, rotate and translate instead of composing a transform string. Pass easing to the same interpolate() call instead of nesting another interpolate() call to create an eased progress value.

Animated position
// 👍 Motion, opacity and scale are visible to the Studio <Interactive.Div name="Prompt bubble" style={{ position: 'absolute', right: 56, top: 96, opacity: interpolate(frame, [30, 50], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }), translate: interpolate(frame, [30, 50], ['0px 32px', '0px 0px'], { easing: Easing.out(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }), scale: interpolate(frame, [30, 50], [0.92, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }), transformOrigin: '100% 100%', }} /> // 👎 The Studio sees computed values instead of editable props const sendStartFrame = 30; const promptTravelDurationInFrames = 20; const promptBubbleTop = interpolate(frame, [30, 50], [128, 96]); const promptBubbleOpacity = interpolate(frame, [30, 50], [0, 1]); const easedProgress = Easing.out(Easing.cubic)( interpolate( frame, [sendStartFrame, sendStartFrame + promptTravelDurationInFrames], [0, 1], ), ); <Interactive.Div name="Prompt bubble" style={{ position: 'absolute', right: 56, top: promptBubbleTop, opacity: promptBubbleOpacity, translate: interpolate(easedProgress, [0, 1], ['0px 32px', '0px 0px']), transformOrigin: '100% 100%', }} />

Use top, left, right and bottom for static layout anchors. When the value changes over time or should be draggable/keyframable in the Studio, put the moving part into translate.

Prompt
Make this interactive in the Remotion Studio: https://www.remotion.dev/docs/studio/interactivity-best-practices.md

Keep composition metadata inline

When scaffolding a composition, keep the component and its <Composition> registration in the same file. Keep static width, height, fps, durationInFrames and defaultProps inline.

The Props editor can save visual edits back to your code when defaultProps is an inline object literal on <Composition> or <Still>. If the Studio shows `defaultProps` prop must be a hardcoded value in the <Composition/> tag, move the values into the JSX.

Composition metadata
// 👍 Static metadata and defaults are inline and nearby <Composition id="my-video" component={MyComponent} durationInFrames={150} fps={30} width={1920} height={1080} defaultProps={{title: 'Hello', color: '#0b84ff'}} /> // 👎 Static metadata is hidden behind helpers or variables const defaultProps = {title: 'Hello', color: '#0b84ff'}; const calculateMetadata = () => { return {durationInFrames: 150, fps: 30, width: 1920, height: 1080}; }; <Composition id="my-video" component={MyComponent} calculateMetadata={calculateMetadata} defaultProps={{ title: 'Hello', }} />

Use calculateMetadata() when metadata depends on dynamic input props, fetched data or asset metadata.

Prompt
Make this interactive in the Remotion Studio: https://www.remotion.dev/docs/studio/interactivity-best-practices.md

Keep effects editable

Keep editable effect parameters inline in the effect call and keep the effect array shape unconditional.

Effects
// 👍 Parameters are inline and the array shape is stable <CanvasImage src={src} width={1280} height={720} effects={[ radialProgressiveBlur({ center: [0.5, 0.5], point1: [0.86, 0.36], point2: [0.68, 0.84], }), ]} /> // 👎 Values are hidden and the effect only exists conditionally const center = [0.5, 0.5] as const; <CanvasImage src={src} width={1280} height={720} effects={enabled ? [ radialProgressiveBlur({ center, }), ] : []} />

Render separate elements if one version should have effects and another should not.

Prompt
Make this interactive in the Remotion Studio: https://www.remotion.dev/docs/studio/interactivity-best-practices.md

See also