Back to Remotion

Submit an Element

packages/docs/elements/submit-an-element.mdx

4.0.4905.8 KB
Original Source

Submit an Element

Have a small, remixable Remotion Element? We'd like to see it.

Follow this checklist from top to bottom when preparing an Element.

1. Choose a focused idea

A strong Element can be previewed, copied into a Remotion project, and used as a video building block.

Good Elements are:

  • Focused on one visual idea, technique, or workflow
  • Copy-pastable and useful in more than one Remotion project
  • Remixable
  • HTML, CSS, and React-first
  • Free of unexpected global styles, layout side effects, and fullscreen assumptions unless it is explicitly a background
  • Building blocks, not compositions: The hosted docs page provides the preview size, frame rate, and duration.

If two variants are worth showing, make two separate Element pages instead of adding a variant prop.

2. Create the Element files

Start from the reusable template in packages/docs/elements-template/.

Add:

  • An Element folder in packages/docs/elements/<category>/<slug>/
  • An index.mdx page
  • One self-contained TSX source file exporting a React component
  • An entry in packages/docs/src/components/Elements/element-definitions.ts

For example:

tsx
export const MyElement = () => {
  return <div style={{width: '100%', height: '100%'}}>...</div>;
};

3. Ensure portability

The source file should work when dragged or copied into a project.

  • Must be a self-contained TSX file
  • Assets must be remote with stable public URLs, no local ones
  • Non-websafe fonts must be loaded through Google Fonts
  • No highly branded videos

4. Define size

To give an Element a fixed size, set elementWidth and elementHeight in its definition entry. Use null for both values if the Element should inherit the full composition dimensions.

tsx
export const elementDefinitions = {
  // The object key must match the slug.
  'overlays/my-element': {
    // Other metadata...
    slug: 'overlays/my-element',
    elementWidth: 900,
    elementHeight: 260,
    previewPadding: 120,
  },
};

Then make the Element fill that area in its source file:

tsx
import {AbsoluteFill} from 'remotion';

export const MyElement = () => {
  return <AbsoluteFill>...</AbsoluteFill>;
};

Size the Element to the smallest useful bounding box.

Use previewPadding in the definition entry if the docs preview needs more breathing room without changing the Element's drag dimensions or adding source padding.

Do not put the wrapper <Sequence> into the Element source file.

5. Separate placement from animation

Let the Studio-inserted <Sequence> handle placement in the video. Don't use left, right, top, and bottom, etc. to position your element.

Inner Element styles can animate entrance values such as translate, scale, and opacity.

For animation, keep the timing inside the component but let the surrounding project decide how long the Element appears:

tsx
import {Interactive, interpolate, useCurrentFrame} from 'remotion';

export const AnimatedElement = () => {
  const frame = useCurrentFrame();

  return (
    <Interactive.Div
      name="Container"
      style={{
        width: '100%',
        height: '100%',
        opacity: interpolate(frame, [0, 20], [0, 1]),
        translate: interpolate(frame, [0, 20], ['0px 40px', '0px 0px']),
      }}
    >
      Hello
    </Interactive.Div>
  );
};

6. Add interactivity where useful

Only add Interactive.* wrappers when they expose useful Studio controls. Every Interactive.* wrapper should have a descriptive name.

Avoid:

  • Redundant nested Interactive.* wrappers that represent the same visual object
  • Repeating the Element display name inside source names; the dragged wrapper already gets named from displayName

For the main inner editable element, use a generic name such as Container.

You should follow Interactivity best practices.

For a single editable object, use one named wrapper:

tsx
import {Interactive, interpolate, useCurrentFrame} from 'remotion';

export const Badge = () => {
  const frame = useCurrentFrame();

  return (
    <Interactive.Div
      name="Container"
      style={{
        opacity: interpolate(frame, [0, 20], [0, 1]),
        translate: interpolate(frame, [0, 20], ['0px 24px', '0px 0px']),
        padding: 24,
        borderRadius: 16,
        backgroundColor: 'white',
      }}
    >
      Launch day
    </Interactive.Div>
  );
};

If a child has separate useful controls, make the child interactive and name it:

tsx
import {Interactive} from 'remotion';

export const Badge = () => {
  return (
    <Interactive.Div
      name="Container"
      style={{
        padding: 24,
        borderRadius: 16,
        backgroundColor: 'white',
      }}
    >
      <Interactive.Div
        name="Title"
        style={{
          color: 'black',
          fontSize: 44,
          fontWeight: 700,
        }}
      >
        Launch day
      </Interactive.Div>
    </Interactive.Div>
  );
};

7. Check the preview assets

Set an intentional posterFrame in the definition entry. It should show the Element in a representative, visible state.

Render the PNG and MP4 locally:

bash
cd packages/docs
bun run render-element-previews

Inspect both files under .element-previews/. The MP4 should cover the Element's full configured duration. Production uploads require maintainer R2 credentials.

8. Open the pull request

When submitting an Element as a pull request, include:

  • The Element folder and definition entry
  • What makes it useful
  • The rendered preview
  • Links to inspiration, if any

Open a pull request on GitHub