React Video Editor
A modular, drop-in React video editor with sidebar panels, Remotion-powered player, renderer abstraction, autosave, theming, and pluggable media adaptors
The RVE Editor is a composable, drop-in editor shell that wires together the core building blocks of a video editing UI: a Remotion-powered player, a modular sidebar with overlay panels, timeline configuration, autosave hooks, theme management, and a renderer abstraction. You stay in control of data and backends via explicit props and adaptors.
Under the hood, RVE uses Remotion for rendering and playback, and exposes a renderer interface (e.g. HTTP/SSR or Lambda) so you can plug in your own backend. Media sourcing is handled by a flexible adaptor layer (e.g. Pexels), while overlays and timeline config remain app-controlled.
Editor vs Player-only
Use full Editor mode for an all-in-one editing UI (sidebar + editor + autosave). Switch to player-only mode when you just need an embeddable, fullscreen player with overlays and responsive sizing, for example on mobile or preview pages.
Key Features
- Renderer abstraction: Bring your own backend via a simple
VideoRendererinterface; ships withHttpRenderer. - Player-only mode: Fullscreen, responsive player without the editor UI for lightweight embeds.
- Modular sidebar: Default sidebar with pluggable overlay panels; disable or replace with your own.
- Autosave hooks:
onSavecallback delivers the full serializedEditorStateon every save, plus visible autosave status. - Theming: Custom theme registry, default themes toggle, and external theme switching.
- Media adaptors: Plug-in sources for video, images, audio, stickers, templates, and animations.
- Timeline config: Control rows, zoom constraints, snapping behavior, and push-on-drag.
- Responsive video sizing: Aspect ratio and explicit dimension controls for predictable layout.
- Mobile-conscious: Player-only mode includes mobile viewport height handling.
Usage Example
Basic (Client-Side Rendering — Default)
import { ReactVideoEditor } from '@reactvideoeditor/react-video-editor/react-video-editor';
import '@reactvideoeditor/react-video-editor/styles.css';
export default function VideoEditorPage() {
return (
<div className="w-full h-screen">
<ReactVideoEditor
projectId="MyComposition"
fps={30}
disabledPanels={[]}
defaultTheme="dark"
showDefaultThemes={true}
/>
</div>
);
}With Server Rendering (Optional)
import React from 'react';
import { HttpRenderer } from '@reactvideoeditor/react-video-editor/utils/http-renderer';
import { ReactVideoEditor } from '@reactvideoeditor/react-video-editor/react-video-editor';
import { createPexelsVideoAdaptor } from '@reactvideoeditor/react-video-editor/adaptors/pexels-video-adaptor';
import { createPexelsImageAdaptor } from '@reactvideoeditor/react-video-editor/adaptors/pexels-image-adaptor';
import '@reactvideoeditor/react-video-editor/styles.css';
export default function VideoEditorPage() {
const PROJECT_ID = 'MyComposition';
const availableThemes = [
{ id: 'rve', name: 'RVE', className: 'rve', color: '#3E8AF5' },
];
const ssrRenderer = React.useMemo(
() =>
new HttpRenderer('/api/latest/ssr', {
type: 'ssr',
entryPoint: '/api/latest/ssr',
}),
[]
);
return (
<div className="w-full h-screen">
<ReactVideoEditor
projectId={PROJECT_ID}
fps={30}
customRenderer={ssrRenderer}
enableWebRender={true}
showRenderSettings={true}
disabledPanels={[]}
availableThemes={availableThemes}
defaultTheme="dark"
sidebarWidth="400px"
sidebarIconWidth="57.6px"
showIconTitles={false}
adaptors={{
video: [createPexelsVideoAdaptor('YOUR_PEXELS_API_KEY')],
images: [createPexelsImageAdaptor('YOUR_PEXELS_API_KEY')],
}}
onThemeChange={(themeId) => console.log('Theme changed:', themeId)}
/>
</div>
);
}Component Structure
ReactVideoEditor/
├── components/
│ ├── core/
│ │ ├── editor.tsx # Main editor surface
│ │ └── video-player.tsx # Remotion-based player
│ ├── shared/
│ │ └── default-sidebar.tsx # Default sidebar with overlay panels
│ ├── autosave/
│ │ └── autosave-status.tsx # Autosave UI indicator
│ ├── providers/
│ │ ├── react-video-editor-provider.tsx # Top-level provider composition
│ │ └── editor-provider.tsx # Core editor context wiring
│ └── ui/ # Sidebar/tooltip/button primitives
├── contexts/
│ ├── editor-context.tsx
│ ├── renderer-context.tsx
│ ├── media-adaptor-context.tsx
│ └── sidebar-context.tsx
├── hooks/
│ ├── use-overlays.tsx
│ ├── use-rendering.tsx
│ └── use-extended-theme-switcher.ts
├── utils/
│ └── http-renderer.ts # HTTP renderer implementing VideoRenderer
├── types/
│ ├── renderer.ts # VideoRenderer interface
│ ├── overlay-adaptors.ts # Overlay adaptor interfaces
│ └── index.ts # OverlayType, Overlay, etc.
└── constants.ts # DEFAULT_OVERLAYS, colors, etc.Props
The ReactVideoEditor component extends the editor provider props and adds UI-level controls. Required props are marked with *.
| Prop | Type | Default | Description |
|---|---|---|---|
| Core | |||
projectId* | string | - | Composition/session ID used for renders and state |
renderer | VideoRenderer | - | Deprecated — use customRenderer instead |
fps | number | 30 | Frames per second for playback and renders |
editorState | EditorState | - | Initial editor state. Pass the object from onSave to restore a previous session |
| Autosave | |||
autoSaveInterval | number | 10000 | Autosave interval in milliseconds |
onSave | (state: EditorState) => void | - | Called on every save (autosave or manual). Receives the full serialized editor state — use this to persist to your backend |
| Sidebar and Layout | |||
showSidebar | boolean | true | Toggle the default sidebar |
customSidebar | ReactNode | - | Provide your own sidebar; hides the default |
sidebarLogo | ReactNode | - | Custom logo for the default sidebar |
sidebarFooterText | string | - | Footer text in the default sidebar |
disabledPanels | OverlayType[] | - | Hide specific overlay panels |
showIconTitles | boolean | true | Show/hide sidebar icon titles |
sidebarWidth | string | "16rem" | CSS width var for content sidebar |
sidebarIconWidth | string | "3rem" | CSS width var for icon rail |
className | string | - | Applied to main content inset |
| Player / Mode | |||
isPlayerOnly | boolean | false | Fullscreen player without editor UI |
isLoadingProject | boolean | false | Whether the project from URL is still loading |
| Renderer & API | |||
customRenderer | VideoRenderer | - | Optional cloud/server renderer (SSR, Lambda, or custom). Not required — the SDK uses client-side rendering by default |
enableWebRender | boolean | true if no customRenderer | Enable client-side video export via WebCodecs. Enabled by default when no customRenderer is provided |
showRenderSettings | boolean | true | Show the render mode toggle in the settings panel |
baseUrl | string | - | Optional API base for editor operations |
| Media Adaptors | |||
adaptors | OverlayAdaptors | - | Pluggable sources for content (video, images, audio, text, stickers, templates, animations) |
| Timeline | |||
initialRows | number | 5 | Initial number of timeline rows |
maxRows | number | 8 | Maximum number of timeline rows |
| Zoom | |||
zoomConstraints | object | { min: 0.2, max: 10, step: 0.1, default: 1 } | Zoom level constraints |
| Snapping | |||
snappingConfig | object | { thresholdFrames: 1, enableVerticalSnapping: true } | Timeline snapping configuration |
| Feature Flags | |||
disableMobileLayout | boolean | false | Disable mobile-specific layout |
disableVideoKeyframes | boolean | false | Disable video keyframe functionality |
enablePushOnDrag | boolean | false | Enable push-on-drag timeline behavior |
| Video Dimensions | |||
videoWidth | number | 1280 | Video output width |
videoHeight | number | 720 | Video output height |
| Theming | |||
availableThemes | CustomTheme[] | - | List of custom themes |
selectedTheme | string | undefined | - | Active theme id |
onThemeChange | (themeId: string) => void | - | Theme change callback |
showDefaultThemes | boolean | true | Show/hide default themes |
hideThemeToggle | boolean | false | Hide theme toggle UI |
defaultTheme | string | "dark" | Default theme to use |
| Status UI | |||
showAutosaveStatus | boolean | true | Show autosave indicator |
Notes
- The SDK ships with client-side rendering enabled by default. You do not need to provide a renderer to get video export working.
- The component internally manages a
playerRefand wires it into theVideoPlayer. If you need direct player access, read it from the editor context or adapt the provider usage directly. - Pass a previously-saved
EditorStatevia theeditorStateprop to seed the editor with initial content. - The
HttpRendereris a convenience implementation ofVideoRendererthat expects REST endpoints at/renderand/progress. It is only needed if you want server-side rendering.
EditorState In, EditorState Out
The onSave callback emits an EditorState on every autosave tick and manual save. Pass that same object back via the editorState prop to restore a session — no destructuring or field mapping required.
import { ReactVideoEditor } from '@reactvideoeditor/react-video-editor/react-video-editor';
import type { EditorState } from '@reactvideoeditor/react-video-editor/types';
function Editor({ savedState }: { savedState?: EditorState }) {
const handleSave = (state: EditorState) => {
// Persist to your backend
fetch('/api/projects/my-project', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(state),
});
};
return (
<ReactVideoEditor
projectId="my-project"
editorState={savedState}
onSave={handleSave}
/>
);
}The editor uses timestamp-based conflict resolution to decide whether to restore from local IndexedDB or use the provided editorState:
editorStatewithsavedAt— compared against the local autosave timestamp. Whichever is newer wins. Local changes made after the last server save are preserved; fresh server data takes priority over stale local state.editorStatewithoutsavedAt— IndexedDB is always restored if a local save exists. The prop is treated as a fallback for when no local state is available.editorStateomitted — falls back to IndexedDB or internal defaults.
The savedAt field is stamped automatically on every save and included in the onSave payload — persist it alongside the rest of the state and pass it back.
The EditorState type contains source-of-truth fields only (no derived values):
interface EditorState {
tracks: Track[];
aspectRatio: AspectRatio; // '16:9' | '1:1' | '4:5' | '9:16'
backgroundColor: string;
playbackRate: number;
savedAt?: number; // Unix-ms timestamp, set automatically on every save
}Need pixel dimensions for an aspect ratio? Use the exported getCanvasDimensions utility:
import { getCanvasDimensions } from '@reactvideoeditor/react-video-editor/types';
const { width, height } = getCanvasDimensions('16:9');
// → { width: 1920, height: 1080 }OPFS asset URLs
Asset URLs from the browser's Origin Private File System (OPFS) are dehydrated to asset:{id} references in the state. If your workflow uses external URLs (e.g. https://), they pass through as-is. OPFS-based assets will need a URL resolution strategy on your backend — this is a separate concern from state persistence.
Deprecated: onSaving / onSaved
The onSaving and onSaved props have been removed. Use onSave instead — it provides the full EditorState on every save, replacing both callbacks with a single, more useful one.
Next Steps
- Get started with the default sidebar panels and overlays — client-side export works out of the box.
- Optionally wire up a server renderer (SSR or Lambda) via
customRendererfor production workloads. - Add media sources via
adaptors(e.g. Pexels for images/videos). - Customize themes and integrate your app's theme switching UX.