/remotion-interactivity
Structure Remotion markup for interactivity
$ npx -y skills add remotion-dev/skills --skill remotion-interactivity --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition โ
- You can call itInvoke it directly when you want it.
- Slash command
/remotion-interactivity
Context preview
The summary Claude sees to decide when to auto-load this skill.
Structure Remotion markup for interactivity
SKILL.md
remotion-interactivity.SKILL.mdname: remotion-interactivity
description: Structure Remotion markup for interactivity
version: 4.0.507
By writing Remotion markup in a specific way, the Remotion Studio is able to recognize the structure of the code and makes it interactive:
- Allowing items to be selected by clicking on them
- Allowing drag+drop, resizing and rotation
- Editing the CSS styles
- Making keyframes and easing values editable
If the markup is too complex for the Studio to make it interactive, then the values become grayed out.
Make an HTML element interactive using `Interactive`
Every HTML and SVG element (except `<Img>`, it already is interactive) such as `<div>` can be turned interactive using `Interactive`:
<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.
Prefer inline text
If text is fixed and only used once, write it directly inside the interactive element instead of extracting it into a constant.
// ๐ 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.
Give interactive elements a descriptive name
Add a `name` prop to elements to make them easily identifyable. Avoid computed names, hardcode them.
<>
<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>
</>Keep all CSS styles inline
The best way is to just pass a plain object to `style` - no referring to constants, no object spreading, no math.
<Interactive.Div
style={{
fontSize: 80,
color: 'red',
}}
>
Hello World!
</Interactive.Div>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>Animate using `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.
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'
}
),
}}
/>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.
}}
/>Use `scale`, `translate`, `rotate` CSS properties
Avoid the `transform` CSS property. If possible, use `scale`, `rotate` and `translate` instead because only they are interactively editable.
Keep composition metadata inline
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>`.
// ๐ 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'}}
/>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 `calculateMetadat
Read more
name: remotion-interactivity description: Structure Remotion markup for interactivity version: 4.0.507
By writing Remotion markup in a specific way, the Remotion Studio is able to recognize the structure of the code and makes it interactive:
- Allowing items to be selected by clicking on them
- Allowing drag+drop, resizing and rotation
- Editing the CSS styles
- Making keyframes and easing values editable
If the markup is too complex for the Studio to make it interactive, then the values become grayed out.
Make an HTML element interactive using `Interactive`
Every HTML and SVG element (except `<Img>`, it already is interactive) such as `<div>` can be turned interactive using `Interactive`:
<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.
Prefer inline text
If text is fixed and only used once, write it directly inside the interactive element instead of extracting it into a constant.
// ๐ 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.
Give interactive elements a descriptive name
Add a `name` prop to elements to make them easily identifyable. Avoid computed names, hardcode them.
<>
<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>
</>Keep all CSS styles inline
The best way is to just pass a plain object to `style` - no referring to constants, no object spreading, no math.
<Interactive.Div
style={{
fontSize: 80,
color: 'red',
}}
>
Hello World!
</Interactive.Div>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>Animate using `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.
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'
}
),
}}
/>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.
}}
/>Use `scale`, `translate`, `rotate` CSS properties
Avoid the `transform` CSS property. If possible, use `scale`, `rotate` and `translate` instead because only they are interactively editable.
Keep composition metadata inline
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>`.
// ๐ 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'}}
/>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 `calculateMetadat
Remotion maintains a list of Agent Skills that define best practices for working in Remotion projects. These skills are useful for AI agents like Claude Code, Codex, Kimi Code or Cursor.
Repo: remotion-dev/skills
Other skills on remotion-dev-skills.
- /remotion-best-practices
Router for all Remotion skills
Open skill - /remotion-captions
Transcribing, displaying and animating captions
Open skill - /remotion-create
Create a new Remotion video
Open skill - /remotion-docs
Search Remotion documentation
Open skill - /remotion-maps
Remotion Map animation knowledge
Open skill - /remotion-markup
Content, animation and effects best practices
Open skill

