Remotion Best Practices
lyonjs/shortvid.io
Best practices for Remotion - Video creation in React. An agent skill from lyonjs/shortvid.io.
Animation components and utilities for Remotion video projects.
$ npx skills add Marve10s/Better-Fullstack --skill remotion-bits -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install Marve10s/Better-Fullstack remotion-bits --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/Marve10s/Better-Fullstack.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/remotion-bits .claude/skills/remotion-bits && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "remotion-bits" agent skill from https://github.com/Marve10s/Better-Fullstack/tree/main/.agents/skills/remotion-bits into .claude/skills/remotion-bits/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "remotion-bits", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/Marve10s/Better-Fullstack/tree/main/.agents/skills/remotion-bitsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add Marve10s/Better-Fullstack --skill remotion-bits -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install Marve10s/Better-Fullstack remotion-bits --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Marve10s/Better-Fullstack.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.agents/skills/remotion-bits .agents/skills/remotion-bits && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "remotion-bits" agent skill from https://github.com/Marve10s/Better-Fullstack/tree/main/.agents/skills/remotion-bits into .agents/skills/remotion-bits/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "remotion-bits", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add Marve10s/Better-Fullstack --skill remotion-bits -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install Marve10s/Better-Fullstack remotion-bits --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Marve10s/Better-Fullstack.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.agents/skills/remotion-bits .cursor/skills/remotion-bits && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "remotion-bits" agent skill from https://github.com/Marve10s/Better-Fullstack/tree/main/.agents/skills/remotion-bits into .cursor/skills/remotion-bits/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "remotion-bits", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/Marve10s/Better-Fullstack.git --path .agents/skills/remotion-bits--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add Marve10s/Better-Fullstack --skill remotion-bits -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install Marve10s/Better-Fullstack remotion-bits --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Marve10s/Better-Fullstack.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.agents/skills/remotion-bits .gemini/skills/remotion-bits && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "remotion-bits" agent skill from https://github.com/Marve10s/Better-Fullstack/tree/main/.agents/skills/remotion-bits into .gemini/skills/remotion-bits/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "remotion-bits", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install Marve10s/Better-Fullstack remotion-bitsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add Marve10s/Better-Fullstack --skill remotion-bits -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/Marve10s/Better-Fullstack.git skills-src && mkdir -p .github/skills && cp -r skills-src/.agents/skills/remotion-bits .github/skills/remotion-bits && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "remotion-bits" agent skill from https://github.com/Marve10s/Better-Fullstack/tree/main/.agents/skills/remotion-bits into .github/skills/remotion-bits/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "remotion-bits", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add Marve10s/Better-Fullstack --skill remotion-bits -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install Marve10s/Better-Fullstack remotion-bits --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Marve10s/Better-Fullstack.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.agents/skills/remotion-bits .opencode/skills/remotion-bits && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "remotion-bits" agent skill from https://github.com/Marve10s/Better-Fullstack/tree/main/.agents/skills/remotion-bits into .opencode/skills/remotion-bits/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "remotion-bits", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
remotion-bitsAnimation components and utilities for Remotion video projects.
Remotion Bits is an agent skill from Marve10s/Better-Fullstack. Animation components and utilities for Remotion video projects. Use when building Remotion compositions with text animations, gradient transitions, particle effects, 3D scenes, or staggered motion effects. Provides example bits (complete compositions) and reusable components that can be installed via jsrepo.
Its SKILL.md is about 8.2k tokens, which your agent loads only when the skill is triggered. The skill folder holds 4 other files, including reference files (for example `references/components.md`, `references/patterns.md` and `references/utilities.md`).
It sits in Media & Creative, covering Video production. It works with Remotion and React. The repository describes itself as: Scaffold production-ready full-stack apps in TypeScript, Rust, Python, Go, and Java with a visual builder and CLI. Choose your frontend, backend, database, auth, AI, payments… The licence is MIT.
9 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit b03f092. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npxnpmFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
unpkg.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Remotion Bits loads about 8.2k tokens when it runs, and up to ~20k if it reads all its reference files. Until then it costs about 81 tokens; SKILL.md has 1,386 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from Marve10s/Better-Fullstack at commit b03f092, republished under its MIT licence (© Marve10s). 1,386 words, ~8,198 tokens.
.claude/skills/remotion-bits/SKILL.md (or your agent's skills folder). This skill also uses 3 other files; get the full folder from GitHub.Animation components and utilities for building Remotion videos. The library's most powerful feature is Scene3D - a camera-based 3D presentation system (like impress.js) that enables cinematic multi-section compositions with flying camera moves, step-aware element animations, and Transform3D position management.
When building any non-trivial composition, prefer Scene3D as your foundation. It handles camera movement, timing, element positioning, and responsive layout all in one system. Individual components (AnimatedText, Particles, etc.) work best as content placed inside Scene3D steps.
Prerequisites: remotion >= 4.0, react >= 18, react-dom >= 18
# Via npm
npm install remotion-bits
# Via jsrepo (copies source for customization)
npx jsrepo init https://unpkg.com/remotion-bits/registry.json
npx jsrepo add animated-text particle-system scene-3dEverything is exported from remotion-bits:
import {
// 3D Scene System (primary workflow)
Scene3D, Step, Element3D, StepResponsive,
Transform3D, Vector3,
useScene3D, useCamera, useActiveStep,
// Animation Components
AnimatedText, AnimatedCounter, TypeWriter, CodeBlock,
StaggeredMotion, GradientTransition, MatrixRain, ScrollingColumns,
Particles, Spawner, Behavior,
// Core
useViewportRect,
interpolate, Easing,
interpolateColorKeyframes, interpolateGradientKeyframes,
random, resolvePoint, createRect,
} from "remotion-bits";Most animation properties accept AnimatedValue: a static number OR an array of keyframes interpolated over the animation's duration.
opacity: 1 // Static
opacity: [0, 1] // Animate 0→1
opacity: [0, 1, 0.5, 0] // Multi-keyframe: 0→1→0.5→0 evenly spaced
scale: [0.8, 1] // Scale from 80% to 100%
y: [30, 0] // Slide up from 30px offsetKeyframes are evenly distributed across the duration. With 4 keyframes over 60 frames: frame 0→20→40→60.
Never hardcode pixel values. Always use viewport-relative units:
const rect = useViewportRect();
// rect.width - composition width (e.g. 1920)
// rect.height - composition height (e.g. 1080)
// rect.vw - 1% of width (19.2)
// rect.vh - 1% of height (10.8)
// rect.vmin - min(vw, vh) - USE THIS for most sizing
// rect.vmax - max(vw, vh)
// rect.cx, cy - center coordinates
const { vmin } = rect;Use vmin for font sizes, element dimensions, spacing, and padding. This ensures compositions render identically at 1920×1080, 1080×1920, 3840×2160, etc.
vmin = min(width, height) / 100. Always compute what vmin equals in pixels for your composition size before choosing multipliers.
| Composition Size | Aspect Ratio | vmin (px) | vmin * 8 | vmin * 10 | vmin * 5 |
|---|---|---|---|---|---|
| 1920×1080 | 16:9 landscape | 10.8 | 86px | 108px | 54px |
| 1080×1920 | 9:16 portrait | 10.8 | 86px | 108px | 54px |
| 1080×1080 | 1:1 square | 10.8 | 86px | 108px | 54px |
| 3840×2160 | 16:9 4K | 21.6 | 173px | 216px | 108px |
| 1280×720 | 16:9 720p | 7.2 | 58px | 72px | 36px |
Use these proven multipliers from production bits. Err on the side of LARGER, not smaller.
| Role | Multiplier | Pixels at 1080p | Example |
|---|---|---|---|
| Hero / main title | vmin * 10–15 | 108–162px | Full-screen headline |
| Section heading | vmin * 6–8 | 65–86px | Scene3D step titles |
| Subheading | vmin * 4–5 | 43–54px | Card titles, counters |
| Body / card label | vmin * 2.5–3 | 27–32px | Feature labels, descriptions |
| Code / small text | vmin * 1.5–2 | 16–22px | Code blocks, captions |
| Fine print | vmin * 1–1.2 | 11–13px | Code font in dense blocks |
Common mistake: using vmin * 3 for headings. At 1080p that's only 32px - fine for body text but too small for any heading. For prominent headings, start at vmin * 8 minimum.
The aspect ratio determines which dimension is the "min" for vmin:
vmin is based on height (1080/100 = 10.8). Horizontal space is abundant; vertical space is limited.vmin is based on width (1080/100 = 10.8). Vertical space is abundant; horizontal space is limited.vmin = vmax = vh = vw. All directions equal.Layout implications:
width: vmin * 70 or maxWidth constraintsvmin for sizes that should scale uniformly regardless of orientationvw/vh when sizing should follow a specific axis (e.g., full-width background: rect.width)| Element | Multiplier | Description |
|---|---|---|
| Card width | vmin * 50–70 | Floating card, info panel |
| Card height | vmin * 25–40 | Match content needs |
| Card border-radius | vmin * 1–2 | Subtle rounding |
| Spacing/gap | vmin * 1–3 | Between elements |
| Padding | vmin * 2–4 | Inside containers |
| Particle size | vmin * 1–3 | Individual particle dot |
| Icon size | vmin * 8–12 | Decorative icons |
Components accept a transition prop with this shape:
transition={{
// Timing
duration: 30, // Duration in frames
delay: 10, // Delay before start
frames: [0, 60], // Or explicit frame range (alternative to duration)
easing: "easeOutCubic", // Easing curve
// Transform (all accept AnimatedValue)
x: [30, 0], // translateX
y: [30, 0], // translateY
z: [0, 100], // translateZ (3D)
scale: [0.8, 1], // uniform scale
scaleX, scaleY, // axis scale
rotate: [0, 360], // 2D rotation (degrees)
rotateX, rotateY, rotateZ, // 3D rotation (degrees)
skew, skewX, skewY, // skew (degrees)
transform: Transform3D[], // 3D transform keyframes (see Transform3D section)
// Visual (all accept AnimatedValue)
opacity: [0, 1],
blur: [10, 0], // Gaussian blur in pixels
borderRadius: [0, 20],
color: ["#ff0000", "#00ff00"], // CSS color interpolation
backgroundColor: ["#000", "#fff"],
}}type EasingName =
| "linear"
| "easeIn" | "easeOut" | "easeInOut"
| "easeInQuad" | "easeOutQuad" | "easeInOutQuad"
| "easeInCubic" | "easeOutCubic" | "easeInOutCubic"
| "easeInSine" | "easeOutSine" | "easeInOutSine"
| "easeInQuart" | "easeOutQuart" | "easeInOutQuart";Most common: "easeOutCubic" for entries, "easeInOutCubic" for camera moves.
CRITICAL: Bits are pre-wrapped with a layout container when displayed in the docs. The wrapper (withShowcaseFill) provides an AbsoluteFill with default styling. When building standalone compositions, you must provide this layout yourself.
Bits displayed in docs are automatically wrapped with:
<AbsoluteFill style={{
backgroundColor: 'var(--color-background-dark)', // #100f0f
fontSize: `${config.width * 0.05}px`, // 5% of width
fontWeight: 700,
color: 'var(--color-primary-hover)', // #fcc192
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
<Component />
</AbsoluteFill>This means simple bits like <AnimatedText>Hello</AnimatedText> work because the wrapper centers them and provides background/color.
When creating compositions for direct use (not displayed through the docs wrapper), you MUST provide your own layout:
import { AbsoluteFill } from 'remotion';
export const MyComposition: React.FC = () => {
const rect = useViewportRect();
const { vmin } = rect;
return (
<AbsoluteFill style={{
backgroundColor: '#100f0f',
color: '#fcc192',
fontFamily: 'system-ui, sans-serif',
}}>
{/* Your content here */}
<Scene3D perspective={1000} stepDuration={60} transitionDuration={60}>
{/* Steps... */}
</Scene3D>
</AbsoluteFill>
);
};Bits use CSS variables for consistent theming. These are available when rendering within the docs but must be defined or replaced with literal values in standalone projects:
| Variable | Default Value | Usage |
|---|---|---|
--color-primary | #ec8b49 | Accent orange |
--color-primary-hover | #fcc192 | Light orange / text color |
--color-background-dark | #100f0f | Dark background |
--color-surface-dark | #1c1b1a | Surface/card background |
--color-surface-light | #343331 | Lighter surface |
--color-border-dark | #100f0f | Dark borders |
--color-border-light | #1c1b1a | Light borders |
In standalone projects: Replace var(--color-*) with literal hex values, or define these variables in your HTML/CSS.
backgroundColor on the outermost container.<AbsoluteFill> from remotion as their root.var(--color-primary) resolves to nothing in a bare Remotion project. Use literal colors.vmin-based sizing from useViewportRect() instead.fontSize, fontWeight, fontFamily, and color explicitly - there are no inherited defaults in Remotion.Simple (centered content, uses wrapper defaults):
// Relies on the outer wrapper for background, centering, font
export const Component = () => (
<AnimatedText transition={{ opacity: [0, 1] }}>Hello</AnimatedText>
);Self-contained (own background and layout):
export const Component = () => (
<div style={{
width: '100%', height: '100%',
backgroundColor: '#09090b',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<StaggeredMotion transition={{ scale: [0, 1], opacity: [0, 1] }}>
{items}
</StaggeredMotion>
</div>
);Full 3D scene (recommended for complex work):
export const Component = () => {
const rect = useViewportRect();
return (
<AbsoluteFill style={{
background: '#100f0f',
color: '#fcc192',
}}>
<Scene3D perspective={1000} stepDuration={60} transitionDuration={60}>
<Step id="intro" {...positions.base.toProps()} />
{/* ... */}
</Scene3D>
</AbsoluteFill>
);
};Transform3D represents a 3D transformation (position + rotation + scale) using Three.js internals. It is immutable by convention - every method returns a new instance.
import { Transform3D, Vector3 } from "remotion-bits";const base = Transform3D.identity(); // Origin: position(0,0,0), rotation(0,0,0), scale(1,1,1)Every method returns a new Transform3D. Chain freely:
const cardPosition = base
.translate(vmin * 50, vmin * -20, 0) // Move right and up
.rotateY(-15) // Rotate around Y axis (degrees)
.scaleBy(1.5); // Scale uniformly by 1.5x// Position
transform.translate(x, y, z) // Add to position
transform.translate(vector3) // Add Vector3 to position
// Rotation (angles in DEGREES)
transform.rotateX(degrees) // Rotate around X axis
transform.rotateY(degrees) // Rotate around Y axis
transform.rotateZ(degrees) // Rotate around Z axis
transform.rotateAround(origin, axis, degrees) // Rotate around arbitrary point+axis
// Scale
transform.scaleBy(uniform) // Scale all axes equally
transform.scaleBy(sx, sy, sz) // Scale per-axis
// Composition
transform.multiply(other) // Matrix multiplication
transform.inverse() // Invert transform
transform.lerp(target, alpha) // Linear interpolation (0-1)
transform.clone() // Deep copy
// Randomization (deterministic via seed)
transform.randomTranslate([minX, maxX], [minY, maxY], [minZ, maxZ], seed)
transform.randomRotateX([minDeg, maxDeg], seed)
transform.randomRotateY([minDeg, maxDeg], seed)
transform.randomRotateZ([minDeg, maxDeg], seed)
// Conversion
transform.toProps() // → { x, y, z, rotateX, rotateY, rotateZ, scaleX, scaleY, scaleZ, rotateOrder }
transform.toCSSMatrix3D() // → "matrix3d(...)" CSS string
transform.toMatrix4() // → Three.js Matrix4The .toProps() method converts a Transform3D to props that Step and Element3D accept:
const position = base.translate(vmin * 50, 0, 0).rotateY(-15);
// Spread directly into Step or Element3D
<Step id="my-step" {...position.toProps()} />This is equivalent to manually specifying x={...} y={...} z={...} rotateX={...} rotateY={...} rotateZ={...}.
Pass Transform3D[] arrays as the transform property for smooth 3D interpolation between positions:
const start = base.translate(0, vmin * 20, 0);
const end = base.translate(0, 0, 0);
<Element3D
centered
transition={{
duration: 35,
transform: [start, end], // Interpolates position, rotation, scale via slerp
opacity: [0, 1],
easing: "easeInOutCubic",
}}
>
<div>Animates from start to end</div>
</Element3D>Transform keyframes use quaternion slerp for rotation (no gimbal lock) and linear interpolation for position and scale.
Three.js Vector3 is re-exported for position math:
const offset = new Vector3(0, -vmin * 2, 0);
const farOffset = offset.clone().multiplyScalar(4.0); // Scale the offset
const combined = offset.clone().add(new Vector3(vmin * 5, 0, 0));Scene3D creates camera-based 3D presentations (like impress.js). The camera flies between Steps; content is placed in 3D space.
Scene3D (perspective, timing)
├── Step (camera target 1) - children visible during this step
├── Step (camera target 2) - children visible during this step
├── ...
├── StepResponsive - element that animates differently per step
├── StepResponsive - another step-aware element
└── (any other children - always rendered)<Scene3D
perspective={1000} // CSS perspective in px (depth effect)
stepDuration={60} // Default frames per step
transitionDuration={60} // Frames for camera transitions between steps
easing="easeInOutCubic" // Camera transition easing
>
{/* Steps and content */}
</Scene3D>Steps define where the camera flies to. They execute sequentially. Content inside a Step is visible when that step is active.
<Step
id="intro" // Unique identifier (used by StepResponsive)
{...position.toProps()} // Camera target position/rotation/scale
duration={120} // Override stepDuration for this step (optional)
transition={{ // Animate children on step ENTRY (optional)
opacity: [0, 1],
blur: [10, 0],
duration: 20,
}}
exitTransition={{ // Animate children on step EXIT (optional)
opacity: [1, 0],
blur: [0, 10],
}}
>
{/* Content shown during this step */}
<FloatingCard>...</FloatingCard>
</Step>Step timing model:
stepDuration (on Scene3D): default duration each step is activeduration (on Step): override for specific steptransitionDuration (on Scene3D): how long the camera takes to move between stepsPlaces content at a specific 3D position, independent of camera:
<Element3D
centered // Center-align the element (transform-origin: center)
x={vmin * 50} y={0} z={0} // Position in 3D space
style={{ width: vmin * 60 }}
transition={{ // Animate on mount (optional)
delay: 20,
opacity: [0, 1],
duration: 35,
transform: [startTransform, endTransform], // 3D keyframes
easing: "easeInOutCubic",
}}
>
<div>Content positioned in 3D space</div>
</Element3D>The key to complex scenes. Elements define how they should look/position at each step, and animate between states when the camera moves:
<StepResponsive
centered // Center the child
style={{ position: 'absolute', fontSize }}
steps={{
// Key = step ID, Value = properties at that step
'intro': {
transform: [base, shiftedPosition], // Transform3D keyframes
opacity: [0, 1],
},
'elements': {
transform: [elementPosition], // Hold at this position
},
'outro': {
transform: [outroStart, outroEnd],
opacity: [1, 1, 1, 0], // Hold visible, then fade
duration: "step", // Match step duration
easing: "easeInOutCubic",
},
}}
>
<h1>Title That Follows Camera</h1>
</StepResponsive>StepResponsive key behaviors:
transform accepts Transform3D[] arrays - the primary way to position elements in 3Dduration: "step" makes the animation last the entire step durationMapping same props to multiple steps (common pattern):
const mapToAllElementSteps = (props) => ({
'elements': props,
'element-particles': props,
'element-text': props,
'element-code': props,
});
<StepResponsive
steps={{
'intro': { transform: [introPosition] },
...mapToAllElementSteps({ transform: [elementPosition] }),
'outro': { transform: [outroPosition] },
}}
>
<h1>Elements</h1>
</StepResponsive>This is the recommended approach for any non-trivial composition. Scene3D provides camera management, step-based timing, 3D element positioning, and step-responsive animations - eliminating the need to manually manage useCurrentFrame(), <Sequence>, or CSS transforms.\n\n### Step 1: Plan the Scene Structure
Decide on the major sections (acts) and what the camera shows in each:
intro → elements → element-particles → element-text → ... → transitions → scenes → outroEach section = one Step. Steps execute sequentially.
Use useMemo to build a position tree. This is THE critical architectural pattern:
const positions = useMemo(() => {
const { vmin } = rect;
const base = Transform3D.identity();
// Define base positions for each scene section
const elementsBase = base.translate(0, -vmin * 120, 0).rotateX(15);
const transitionsBase = base.translate(vmin * 200, vmin * 50, 0).rotateY(-15);
const scenesBase = base.translate(-vmin * 120, vmin * 70, 0).rotateY(15);
// Derive sub-positions from bases
const cardW = vmin * 70;
const particlesCard = elementsBase.translate(-cardW, -vmin * 40, 0).rotateY(15);
const textCard = elementsBase.translate(0, -vmin * 50, vmin * 10).rotateX(10);
return {
base,
elements: {
base: elementsBase,
cards: { particles: particlesCard, text: textCard },
},
transitions: { base: transitionsBase },
scenes: { base: scenesBase },
};
}, [rect.width, rect.height]); // Re-compute on resizeWhy pre-compute positions?
<Scene3D perspective={1000} stepDuration={60} transitionDuration={60}>
<Step id="intro" {...positions.base.toProps()} />
<Step id="elements" {...positions.elements.base.toProps()} />
<Step id="element-particles"
{...positions.elements.cards.particles.toProps()}
transition={{ opacity: [0, 1], blur: [10, 0] }}
>
<FloatingCard><ParticleDemo /></FloatingCard>
</Step>
<Step id="transitions" duration={120} {...positions.transitions.base.toProps()} />
<Step id="scenes" duration={120} {...positions.scenes.base.toProps()} />
<Step id="outro" {...positions.base.toProps()} duration={120} />
</Scene3D>Titles, icons, and persistent elements that move with the camera:
<StepResponsive
centered
style={{ fontSize: vmin * 10, position: 'absolute' }}
steps={{
'intro': { transform: [positions.base, positions.base.translate(vmin * 7, 0, 0)] },
'elements': { transform: [positions.elements.base.translate(vmin * 7, 0, 0)] },
'outro': {
transform: [positions.base.translate(vmin * 7, 0, 0), positions.base],
duration: "step",
easing: "easeInOutCubic",
},
}}
>
<h1>Remotion Bits</h1>
</StepResponsive>Steps can contain rich content - cards, particles, code blocks, counters:
<Step id="element-particles" {...cardPos.toProps()}
transition={{ opacity: [0, 1], blur: [10, 0] }}>
<FloatingCard>
<Particles style={{ position: 'absolute', inset: 0, opacity: 0.6 }}>
<Spawner rate={1} max={200} lifespan={80}
velocity={{ x: 0, y: -0.6, varianceX: 0.4, varianceY: 0.2 }}
area={{ width: rect.width, height: rect.height }}>
<div style={{ width: vmin * 2, height: vmin * 2, borderRadius: '50%',
background: 'var(--color-primary)' }} />
</Spawner>
<Behavior drag={0.96}
wiggle={{ magnitude: 0.6, frequency: 0.25 }}
opacity={[1, 0]}
scale={{ start: 1, end: 0.4 }} />
</Particles>
<span style={{ position: 'relative', zIndex: 1, fontWeight: 'bold',
fontSize: vmin * 3, fontFamily: 'monospace' }}>Particles</span>
</FloatingCard>
</Step><AnimatedText
style={{ fontSize: vmin * 5, fontWeight: 'bold' }}
transition={{
split: "character", // "none" | "word" | "character" | "line" | custom separator
splitStagger: 2, // Frames between each unit
opacity: [0, 1],
y: [15, 0],
blur: [2, 0],
duration: 20,
delay: 10,
easing: "easeOutCubic",
// Cycling text:
cycle: { texts: ["Build", "Create", "Ship"], itemDuration: 40 },
// Glitch effect:
glitch: [0.6, 0], // Glitch intensity AnimatedValue
}}
>
Hello World
</AnimatedText><AnimatedCounter
transition={{
values: [0, 1000], // Number keyframes
color: ['#ff0000', '#00ff00'], // Color transition
scale: [0.8, 1],
duration: 30,
delay: 10,
}}
prefix="$"
postfix="+"
toFixed={0}
style={{ fontSize: vmin * 4, fontWeight: 'bold' }}
/><TypeWriter
text="import { TypeWriter } from 'remotion-bits';"
typeSpeed={2} // Frames per character
deleteSpeed={1}
pauseAfterType={60} // Frames to wait before deleting
delay={30} // Start delay
cursor="▋"
style={{ fontSize: vmin * 2, fontFamily: 'monospace', whiteSpace: 'pre' }}
/>
// Multiple texts with cycling:
<TypeWriter
text={["First line", "Second line", "Third line"]}
deleteBeforeNext={true}
loop={false}
errorRate={0.05} // 5% chance of typo
/><CodeBlock
code={`const x = useViewportRect();\nconsole.log(x.vmin);`}
language="tsx"
theme="dark"
showLineNumbers={false}
fontSize={vmin * 1.2}
padding={vmin * 1.5}
transition={{
duration: 20,
delay: 10,
lineStagger: 2, // Frames between lines
lineStaggerDirection: "forward", // "forward" | "reverse" | "center" | "random"
opacity: [0, 1],
y: [8, 0],
blur: [10, 0],
}}
highlight={[{ lines: [2, 3], color: "rgba(59,130,246,0.15)", opacity: [0, 1] }]}
focus={{ lines: [2, 3], dimOpacity: 0.3, dimBlur: 2 }}
/><StaggeredMotion
transition={{
opacity: [0, 1],
scale: [0, 1],
y: [20, 0],
duration: 30,
delay: 5,
stagger: 3, // Frames between each child
staggerDirection: "forward", // "forward"|"reverse"|"center"|"random"
easing: "easeOutCubic",
borderRadius: 4,
}}
>
<div>Child 1</div>
<div>Child 2</div>
<div>Child 3</div>
</StaggeredMotion><GradientTransition
gradient={[
"linear-gradient(0deg, #051226, #1e0541)",
"linear-gradient(180deg, #a5d4dd, #5674b1)",
]}
duration={90}
easing="easeInOut"
shortestAngle={true} // Interpolate angles via shortest path
/><MatrixRain fontSize={18} color="#00FF00" speed={1.2} density={0.8} streamLength={20} /><ScrollingColumns
columns={[
{ images: ["/img1.jpg", "/img2.jpg"], speed: 100, direction: "up" },
{ images: ["/img3.jpg", "/img4.jpg"], speed: 150, direction: "down" },
]}
height={rect.vmin * 40}
gap={rect.vmin * 2}
/><Particles style={{ position: 'absolute', inset: 0 }}>
<Spawner
rate={2} // Particles per frame
max={200} // Maximum alive particles
lifespan={100} // Frames each particle lives
burst={50} // Emit 50 on first frame (optional)
position={resolvePoint(rect, "center")}
area={{ width: rect.width, height: 0 }} // Spawn area
velocity={{ x: 0, y: -2, varianceX: 1, varianceY: 0.5 }}
>
{/* Multiple children = random selection per particle */}
<div style={{ width: vmin * 2, height: vmin * 2, borderRadius: '50%', background: '#fff' }} />
<div style={{ width: vmin * 1, height: vmin * 1, borderRadius: '50%', background: '#aaa' }} />
</Spawner>
<Behavior
gravity={{ y: 0.1 }} // Constant force
drag={0.96} // Air resistance (1=none, 0=instant stop)
wiggle={{ magnitude: 0.6, frequency: 0.25 }} // Random oscillation
opacity={[1, 0]} // Fade out over lifetime
scale={{ start: 1, end: 0.3, startVariance: 0.2 }} // Shrink over lifetime
/>
</Particles>For grids, backgrounds, and data-driven visuals, use deterministic random():
import { random } from 'remotion-bits';
const items = useMemo(() => {
const palette = ['#fb4934', '#b8bb26', '#fabd2f', '#83a598'];
const result = [];
for (let r = 0; r < 10; r++) {
for (let c = 0; c < 20; c++) {
const seed = `grid-${r}-${c}`;
const colorIndex = Math.floor(random(seed + 'color') * palette.length);
const dist = Math.sqrt(r * r + c * c);
result.push(
<StaggeredMotion key={`${r}-${c}`}
transition={{
opacity: [0, 1],
scale: [0, 1],
delay: 30 + dist * 2, // Wave from top-left
duration: 40,
}}>
<div style={{
width: vmin * 5, height: vmin * 5,
background: palette[colorIndex],
}} />
</StaggeredMotion>
);
}
}
return result;
}, [vmin]);random(seed) is deterministic - same seed always returns same value. Use unique string seeds per element.
Unlike Remotion's built-in, supports non-monotonic input ranges:
import { interpolate, Easing } from "remotion-bits";
// Hold then animate
interpolate(frame, [0, 30, 30, 60], [0, 0, 100, 100]);
// With easing
interpolate(frame, [0, 60], [0, 100], { easing: "easeOutCubic" });
// Clamp extrapolation
interpolate(frame, [0, 60], [0, 100], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });import { interpolateColorKeyframes } from "remotion-bits";
// Perceptually uniform (Oklch) color transitions
const color = interpolateColorKeyframes(["#ff0000", "#00ff00", "#0000ff"], progress);import { resolvePoint, createRect, Rect } from "remotion-bits";
const point = resolvePoint(rect, "center"); // { x: 960, y: 540 }
const point2 = resolvePoint(rect, { x: "50%", y: "25%" }); // { x: 960, y: 270 }Bits export a metadata object and a Component:
export const metadata = {
name: "MyBit",
description: "Description of the bit.",
tags: ["tag1", "tag2"],
duration: 300, // Total frames
width: 1920,
height: 1080,
registry: {
name: "bit-my-bit",
title: "My Bit",
description: "Registry description.",
type: "bit" as const,
add: "when-needed" as const,
registryDependencies: ["animated-text", "scene-3d", "use-viewport-rect"],
dependencies: [],
files: [{ path: "path/to/MyBit.tsx" }],
},
};
export const Component: React.FC = () => { /* ... */ };const FloatingCard = ({ children }) => (
<div style={{
position: 'relative',
width: vmin * 60, height: vmin * 30,
background: 'rgba(20, 20, 30, 0.6)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.2)',
borderRadius: vmin * 1,
display: 'flex', alignItems: 'center', justifyContent: 'center',
overflow: 'hidden',
boxShadow: '0 0 20px rgba(0,0,0,0.2)',
}}>
{children}
</div>
);<Step id="particles-demo" {...cardPosition.toProps()}
transition={{ opacity: [0, 1], blur: [10, 0] }}>
<FloatingCard>
{/* Content appears when camera arrives */}
</FloatingCard>
</Step><StepResponsive
centered
style={{ fontSize: vmin * 10, position: 'absolute', width: 'max-content' }}
steps={{
'intro': { transform: [startPos] },
'main': { transform: [mainPos] },
'outro': { transform: [mainPos, endPos], duration: "step", easing: "easeInOutCubic" },
}}
>
<h1>Section Title</h1>
</StepResponsive>const dist = Math.sqrt(Math.pow(r - centerRow, 2) + Math.pow(c - centerCol, 2));
const delay = 30 + dist * 2; // Ripple outward from center
<StaggeredMotion transition={{ opacity: [0, 1], scale: [0, 1], delay, duration: 40 }}>
<div style={{ width: size, height: size, background: color }} />
</StaggeredMotion><Step id="transitions" duration={120} {...pos.toProps()}
exitTransition={{ blur: [0, 10], opacity: [1, 0] }}>
{/* Content blurs out when camera leaves */}
</Step><Element3D centered
style={{ width: vmin * 32, height: vmin * 24 }}
transition={{
delay: 10,
opacity: [0, 1],
duration: 35,
transform: [
base.translate(0, vmin * 20, 0), // Start below
base.translate(0, 0, 0), // End at position
],
easing: 'easeInOutCubic',
}}>
<div>slides up into place</div>
</Element3D>For exhaustive API details:
© Marve10s, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 3 other files (references) in .agents/skills/remotion-bits of Marve10s/Better-Fullstack.
Open the folder on GitHubat commit b03f092
We found 1 copy of this SKILL.md (exact, near-identical or edited) in other folders, from 1 other GitHub owner. This page covers the copy in Marve10s/Better-Fullstack, which our catalogue first saw on October 7, 2026.
Remotion Bits next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Remotion Bits this skillMarve10s/Better-Fullstack | 752 | 1 repos | ~8.2k | Automated safety check: Pass | MIT | |
| Remotion Best Practiceslyonjs/shortvid.io | 147 | 33 repos | ~1k | Automated safety check: Pass | MIT | |
| Remotion to HyperFrames Porterheygen-com/hyperframes | 59k | 3 repos | ~2.8k | Automated safety check: Pass | Apache-2.0 | |
| Anything2explainerVincentwei1021/anything2explainer | 2.3k | — | ~2.7k | Automated safety check: Pass | Custom licence | |
| Remotion Motion Graphicshaidrrrry/claude-remotion-skill | 277 | — | ~2k | Automated safety check: Pass | MIT | |
| Product DemoAlexwtlf/agentic-product-demo | 370 | — | ~8.8k | Automated safety check: Pass | Custom licence |
lyonjs/shortvid.io
Best practices for Remotion - Video creation in React. An agent skill from lyonjs/shortvid.io.
heygen-com/hyperframes
Ports an existing Remotion (React) composition to HyperFrames HTML with GSAP, one way, and grades the result against a tiered set of reference fixtures.
Vincentwei1021/anything2explainer
给一个主题,产出一条黑底 MG 风格(幕底可选星点或点阵波)、有配音字幕章节进度条的科普讲解视频(中文或英文;Remotion 代码动画;时长由用户定,常用 3–5 分钟)。内含可编译模板、图元库、配音/分镜/渲染工具、风格与动效规范、多 agent 分工协议与 QC 判据,以及一条完整样片(《RAG 与知识库》)作为质量标尺。Turn any topic into a narrated…
haidrrrry/claude-remotion-skill
Create and edit professional motion graphics videos with Remotion (React-based video).
Alexwtlf/agentic-product-demo
Shoot, animate, title and optionally score product demo videos with Remotion — for a landing page, docs, onboarding, an app store listing or a social post.
chmonitor/chmonitor
Port an existing Remotion (React) composition to HyperFrames HTML.
Marve10s/Better-Fullstack
Create Remotion videos using the Geist design system aesthetic.
Marve10s/Better-Fullstack
Scaffold, plan, or extend Better Fullstack projects with the generator, CLI, or MCP server.
Marve10s/Better-Fullstack
Create modern product launch/pitch videos using Remotion. An agent skill from Marve10s/Better-Fullstack.
Marve10s/Better-Fullstack
Generates animation configurations for Remotion including spring configs, interpolations, easing functions, and timing logic.
Marve10s/Better-Fullstack
Add capabilities, addons, deployment targets, generated CI, services, or stack updates to an existing Better Fullstack project.
Marve10s/Better-Fullstack
Scaffold a new app, API, backend, fullstack project, mobile app, polyglot service, monorepo, or starter with Better Fullstack.
Categories
Animation components and utilities for Remotion video projects. Remotion Bits is an agent skill from Marve10s/Better-Fullstack. Animation components and utilities for Remotion video projects.
Remotion Bits fits situations like: building Remotion compositions with text animations; gradient transitions; particle effects; staggered motion effects.
Run `npx skills add Marve10s/Better-Fullstack --skill remotion-bits -a claude-code`. Or copy the skill folder (.agents/skills/remotion-bits in Marve10s/Better-Fullstack) into .claude/skills/remotion-bits in your project. Claude Code loads it when a task matches its description.
Run `npx skills add Marve10s/Better-Fullstack --skill remotion-bits -a codex`. Or copy the skill folder (.agents/skills/remotion-bits in Marve10s/Better-Fullstack) into .agents/skills/remotion-bits in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add Marve10s/Better-Fullstack --skill remotion-bits -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/remotion-bits, .gemini/skills/remotion-bits, .github/skills/remotion-bits and .opencode/skills/remotion-bits in your project.
Going by SKILL.md and its folder, Remotion Bits needs the command-line tools its instructions call (npx and npm). Our summary lists: Node.js.
SKILL.md names 1 domain. In commands or code: unpkg.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Remotion Bits is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 8.2k tokens (SKILL.md is roughly 33k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 12k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Remotion Bits: Remotion Best Practices (lyonjs/shortvid.io, 147 stars), Remotion to HyperFrames Porter (heygen-com/hyperframes, 59k stars), Anything2explainer (Vincentwei1021/anything2explainer, 2.3k stars) and Remotion Motion Graphics (haidrrrry/claude-remotion-skill, 277 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
Marve10s (a GitHub user) maintains it in Marve10s/Better-Fullstack, which has 752 GitHub stars. The repository holds 8 skills in this directory. The repository was last updated on October 7, 2026.
Source: Marve10s/Better-Fullstack on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.