---
image: /generated/articles-docs-studio-interactivity-best-practices.png
title: Interactivity best practices
crumb: "Remotion Studio"
---

# Interactivity best practices

Write editable Remotion components so the [Studio interactivity](/docs/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 {#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()`](/docs/interactive-with-schema) 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.
- Write animations as inline `interpolate()` calls.
- Keep `interpolate()` input and output ranges as hardcoded 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-interactive-for-tweakable-elements}

Use the [`Interactive`](/docs/interactive) namespace when an HTML or SVG element should be selected, moved or edited in the Studio.

```tsx title="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](/docs/studio/make-component-interactive).

## Give interactive elements a descriptive name {#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.

```tsx title="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 {#keep-editable-values-visible}

<SuggestedPrompt prompt="/remotion-interactivity Inline the values to make them editable." />

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

Write animations as inline `interpolate()` calls on the property that changes.
Do not calculate the value in a local variable and pass the variable into `style`.
Do not animate with frame math such as `frame * 2`, modulo expressions, helper functions or transform strings.
Keep the input range and output range directly in the `interpolate()` call.
The input range must use hardcoded numbers, not variables or expressions such as `[sendStartFrame, sendStartFrame + duration]`.

```tsx title="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()`](/docs/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.

```tsx title="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`.

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

## Keep composition metadata inline {#keep-composition-metadata-inline}

When scaffolding a composition, keep the component and its [`<Composition>`](/docs/composition) registration in the same file.
Keep static `width`, `height`, `fps`, `durationInFrames` and [`defaultProps`](/docs/composition#defaultprops) inline.

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

```tsx title="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()`](/docs/calculate-metadata) when metadata depends on dynamic input props, fetched data or asset metadata.

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

## Keep effects editable {#keep-effects-editable}

Keep editable effect parameters inline in the effect call and keep the effect array shape unconditional.
If an effect parameter is animated, write it as an inline `interpolate()` call in the effect call.

```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,
      rotation: interpolate(frame, [0, 120], [0, 180]),
    }),
  ]}
/>

// 👎 Values are hidden and the effect only exists conditionally
const center = [0.5, 0.5] as const;
const rotation = frame * 1.5;
<CanvasImage
  src={src}
  width={1280}
  height={720}
  effects={enabled ? [
    radialProgressiveBlur({
      center,
      rotation,
    }),
  ] : []}
/>
```

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

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

## See also

- [Studio interactivity](/docs/studio/interactivity)
- [Make a component interactive](/docs/studio/make-component-interactive)
- [Visual editing](/docs/visual-editing)
- [`Interactive`](/docs/interactive)
- [`interpolate()`](/docs/interpolate)
- [`Easing.spring()`](/docs/easing#spring)
