{"id":24727,"plugin_id":"plugins~Plugin_efd07789186881918253a50acfc32762","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:18:36.229Z","digest":"27c8ea6bc889150fc2f4e0a6825bddb4a6062ceeef79d1b5bbb8a57af387a8d4","against":null,"payload":{"name":"remotion-interactivity","description":"Structure Remotion markup for interactivity","included_files":[{"relative_path":"agents/openai.yaml","size_in_bytes":320},{"relative_path":"assets/remotion-icon.png","size_in_bytes":1286}],"skill_md_contents":"---\nname: remotion-interactivity\ndescription: Structure Remotion markup for interactivity\nversion: 4.0.506\n---\n\nBy writing Remotion markup in a specific way, the Remotion Studio is able to recognize the structure of the code and makes it interactive:\n\n- Allowing items to be selected by clicking on them\n- Allowing drag+drop, resizing and rotation\n- Editing the CSS styles\n- Making keyframes and easing values editable\n\nIf the markup is too complex for the Studio to make it interactive, then the values become grayed out.\n\n## Make an HTML element interactive using `Interactive`\n\nEvery HTML and SVG element such as `<div>` can be turned interactive using `Interactive`:\n\n```tsx title=\"Interactive elements\"\n<Interactive.Div\n  name=\"Greeting card\"\n  style={{fontSize: 80, padding: 24}}\n>\n  Hello\n</Interactive.Div>\n```\n\nThis allows styles and keyframes to be set in the Studio. Be sensible, if a component has many elements, the timeline might get messy.\n\n## Prefer inline text\n\nIf text is fixed and only used once, write it directly inside the interactive element instead of extracting it into a constant.\n\n```tsx title=\"Inline text\"\n// 👍 Fixed copy stays editable\n<Interactive.Div name=\"Title\">\n  Remotion Best Practices\n</Interactive.Div>\n```\n\nUse a prop or variable only when the text is dynamic or reused.\n\n## Give interactive elements a descriptive name\n\nAdd a `name` prop to elements to make them easily identifyable.\n\n```tsx title=\"Interactive names\"\n<>\n  <Interactive.Div name=\"Hero title\" style={{fontSize: 80}}>\n    Launch day\n  </Interactive.Div>\n  <Img name=\"Avatar\" src=\"https://remotion.media/image.jpeg\" />\n  <Video name=\"Background\" src=\"https://remotion.media/video.mp4\" />\n  <Sequence name=\"Title\">\n    Launch day\n  </Sequence>\n</>\n```\n\n## Keep all CSS styles inline\n\nThe best way is to just pass a plain object to `style` - no referring to constants, no object spreading, no math.\n\n```tsx title=\"Interactive example\"\n<Interactive.Div\n  style={{\n    fontSize: 80,\n    color: 'red',\n  }}\n>\n  Hello World!\n</Interactive.Div>\n```\n\n```tsx title=\"❌ Bad for interactivity\"\nconst baseStyle = useMemo(() => {\n  return {\n    fontSize: 12 // ❌ Non-inline styles are not supported\n  }\n}, []);\n\n<Interactive.Div\n  style={{\n    ...baseStyle, // ❌ Spreading is not supported\n    color: RED, // ❌ Referring to constants is not supported\n    scale: frame * 10 // ❌ Math is not supported\n  }}\n>\n  Hello World!\n</Interactive.Div>\n```\n\n## Animate using `interpolate()`\n\nWrite animations as inline `interpolate()` calls on the property that changes.  \nThe output range, easing, extrapolation and `output` property should use hardcoded values.\n\nThe 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.\n\n```tsx title=\"Inline values\"\nconst {fps, durationInFrames} = useVideoConfig();\n\n// 👍 Inline values can be standardized and keyframed\n<Interactive.Div\n  name=\"Product card\"\n  style={{\n    color: 'white',\n    fontSize: 80,\n    scale: interpolate(frame, [0, fps], [0, 1], {\n      easing: Easing.spring({damping: 200}),\n      output: 'perceptual-scale',\n      extrapolateLeft: 'clamp',\n      extrapolateRight: 'clamp'\n    }),\n    rotate: interpolate(frame, [0, 1 * fps], ['0deg', '20deg'], {\n      easing: Easing.spring({damping: 200}),\n      extrapolateLeft: 'clamp',\n      extrapolateRight: 'clamp'\n    }),\n    translate: interpolate(\n      frame,\n      [durationInFrames - 30, durationInFrames],\n      ['0px 0px', '0px 120px'],\n      {\n        easing: Easing.spring({damping: 200}),\n        output: 'perceptual-scale',\n        extrapolateLeft: 'clamp',\n        extrapolateRight: 'clamp'\n      }\n    ),\n  }}\n/>\n```\n\n```tsx title=\"❌ Bad interactivity\"\nconst translateY = interpolate(frame, [0, 30], [0, 120]); // ❌ Math should be directly in the markup\n\n<Interactive.Div\n  name=\"Product card\"\n  style={{\n    translate: translateY, // ❌ Only inline interpolate() calls are supported,\n    rotate: interpolate(frame, [start, start + 10], [0, Math.PI]), // ❌ Cannot use math with arbitrary variables, cannot use constants\n    scale: interpolate(anyVariable, [0, 30], [0, 1]) // ❌ Can only interpret the `frame` variable.\n  }}\n/>\n```\n\n## Use `scale`, `translate`, `rotate` CSS properties\n\nAvoid the `transform` CSS property.  \nIf possible, use `scale`, `rotate` and `translate` instead because only they are interactively editable.\n\n## Keep composition metadata inline\n\nWhen scaffolding a composition, keep `width`, `height`, `fps`, `durationInFrames` and `defaultProps` inline and make no type assertions.\n\nThe Props editor can save visual edits back to your code when `defaultProps` is an inline object literal on `<Composition>` or `<Still>`.\n\n```tsx\n// 👍 Static values are in <Composition>, dynamic values are in calculateMetadata()\nconst calculateMetadata = useMemo(async () => {\n  const dimensions = await getDimensions(); // just an example\n  return {width: dimensions.width, height: dimensions.height};\n});\n\n<Composition\n  id=\"my-video\"\n  component={MyComponent}\n  durationInFrames={150}\n  fps={30}\n  calculateMetadata={calculateMetadata}\n  defaultProps={{title: 'Hello', color: '#0b84ff'}}\n/>\n```\n\n```tsx title=\"Negative examples\"\nconst defaultProps = {title: 'Hello', color: '#0b84ff'}; // ❌ Don't extract defaultProps, must be inline\nconst calculateMetadata = useMemo(() => {\n  // ❌ Unnecessary because no calculation is being done,\n  return {durationInFrames: 150, fps: 30, width: 1920, height: 1080};\n});\n\n<Composition\n  id=\"my-video\"\n  component={MyComponent}\n  calculateMetadata={calculateMetadata}\n  defaultProps={{\n    title: 'Hello',\n  } as Props} // ❌ Don't have type assertions, instead type MyComponent correctly\n/>\n```\n\nUse only `calculateMetadata()` for the part of the metadata that is dynamic.\n\n## Effects should be inline too\n\nThe effects array should not be computed.  \nThe same rules for setting keyframes as `interpolate()` apply too here: All values should also be hardcoded: Input range, output range, easing, extrapolation, `output` property.\n\n```tsx title=\"Effects\"\n// 👍 Parameters are inline and the array shape is stable\n<CanvasImage\n  src={src}\n  width={1280}\n  height={720}\n  effects={[\n    radialProgressiveBlur({\n      center: [0.5, 0.5],\n      width: 1.2,\n      height: 0.8,\n      start: 0.2,\n      disabled: true,\n      rotation: interpolate(frame, [0, 120], [0, 180]),\n    }),\n  ]}\n/>\n\nconst center = [0.5, 0.5] as const;\nconst rotation = frame * 1.5;\n\n<CanvasImage\n  src={src}\n  width={1280}\n  height={720}\n  // ❌ Conditional effect is not animateable\n  effects={enabled ? [\n    radialProgressiveBlur({\n      // ❌ Not inline\n      center,\n      rotation,\n    }),\n  ] : []}\n/>\n```\n\nRender separate elements if one version should have effects and another should not.\n\n## Making your own component interactive\n\nTo make a custom userland component interactive, use:\n[Make a component interactive](https://www.remotion.dev/docs/studio/make-component-interactive.md)\n\n## Video editing\n\nIf a Remotion component mainly consists of video and audio clips, see [Video editing](../remotion-markup/video-editing.md) for best practices on how to structure Remotion markup so the clips are interactively editable in the timeline.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}