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}
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-render.ts
│ └── 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 |
exportOptions | ExportOptions | { availableResolutions: ['720p', '1080p', '4k'], defaultResolution: '1080p' } | Configure the resolution presets shown in the export dialog. See Export Options |
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 |
| Watermark | |||
watermark | WatermarkConfig | true | Watermark on your videos. true for default RVE badge, { src: '...' } for custom image, false for none. See Watermark |
| 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.
Export Options
The exportOptions prop controls the resolution presets shown in the export dialog. When a user clicks "Export", they see a dialog to choose a resolution before rendering begins. The pixel dimensions are calculated automatically based on the current aspect ratio.
<ReactVideoEditor
projectId="my-project"
exportOptions={{
availableResolutions: ['720p', '1080p', '4k'],
defaultResolution: '1080p',
}}
/>| Property | Type | Default | Description |
|---|---|---|---|
availableResolutions | ('720p' | '1080p' | '4k')[] | ['720p', '1080p', '4k'] | Resolution presets offered in the export dialog |
defaultResolution | '720p' | '1080p' | '4k' | '1080p' | Resolution selected when the dialog opens |
The resolutions map to the short edge of the output — for a 16:9 aspect ratio, 1080p produces 1920×1080, while for 9:16 it produces 608×1080. The dialog shows the calculated pixel dimensions for each option.
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.