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, text, templates, Lottie, animations, and transcription.
- Timeline config: Control zoom constraints, snapping behavior, and push-on-drag.
- Shapes, keyframes, and templates: See Shapes, Keyframes, and Templates.
- 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>
);
}Package Entry Points
You can import only from the entry points that the package exports. Internal source files are not part of the public API.
| Entry point | Contents |
|---|---|
/react-video-editor | The ReactVideoEditor component |
/styles.css | The compiled editor stylesheet |
/types | Public types (EditorState, Track, Item, adaptor types, export types) and helpers such as getCanvasDimensions, estimateExportSize, and PAPER_PRESETS |
/constants | Editor constants |
/hooks/use-render | The standalone useRender hook |
/hooks/use-project-state-from-url, /hooks/use-alignment-guides | Helper hooks |
/utils/http-renderer | HttpRenderer, a VideoRenderer for HTTP render endpoints |
/utils/remotion/aws-lambda/deploy | The Lambda deploy helper |
/adaptors/pexels-video-adaptor, /adaptors/pexels-image-adaptor, /adaptors/whisper-transcription-adaptor | Adaptor factories |
/remotion/components/*-layer-content, /remotion/layer-box, /remotion/layer-z-index | Building blocks for a custom composition |
/data/google-fonts, /data/google-fonts-list | The Google Fonts data |
/mobile-warning-modal, /project-load-confirm-modal | Standalone dialogs |
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 | string[] | [] | Hide sidebar panels by id. See the panel ids |
showIconTitles | boolean | true | Show/hide sidebar icon titles |
sidebarWidth | string | "256px" ("400px" with screenRecordingTools) | CSS width of the content panel |
sidebarIconWidth | string | "48px" | CSS width of the icon rail |
className | string | - | Applied to main content inset |
screenRecordingTools | boolean | false | Add and open the beta screen-recording Home panel for backgrounds, screen framing, camera layouts, borders, cursor controls, and automatic zoom. See Screen Recorder mode |
| Player / Mode | |||
isPlayerOnly | boolean | false | Fullscreen player without editor UI |
isLoadingProject | boolean | false | Whether the project from URL is still loading |
timelineHeaderConfig | TimelineHeaderConfig | Mode defaults | Show or hide the aspect-ratio and playback-speed controls. Screen Recorder mode hides both controls by default |
enableCanvasZoom | boolean | true | Enable wheel zoom, wheel pan, and drag pan on the editor canvas. This does not change video zoom effects |
showAlignmentGuides | boolean | true | Show alignment guides when you move items on the canvas |
showBuiltWithRve | boolean | false | Show a "Built with RVE" link badge on the player |
showBrowserWarning | boolean | true | Show a dialog when the browser does not support features such as video export and audio processing |
browserWarningMessage | ReactNode | - | Custom content for the browser warning dialog |
showMobileWarning | boolean | true | Show a one-time dialog on mobile screens. The dialog tells the user that the editor works best on a desktop |
| Renderer & API | |||
customRenderer | VideoRenderer | - | Optional cloud/server renderer (SSR, Lambda, or custom). Not required — the SDK uses client-side rendering by default. If the renderer is enabled, the editor starts in cloud mode |
enableWebRender | boolean | true if no customRenderer | Enable client-side video export via WebCodecs. Enabled by default when no customRenderer is provided |
webRender | boolean | - | Deprecated — use enableWebRender instead |
exportOptions | ExportOptions | { availableResolutions: ['720p', '1080p', '4k'], defaultResolution: '1080p', quality: 'high' } | Configure the resolution presets in the export dialog and the quality of browser exports. See Export Options |
disableExportModal | boolean | false | Skip the resolution dialog. The Render button exports immediately at the exact composition size. See Composition Dimensions |
defaultExportScale | number | - | Render multiplier applied by the built-in Render button, so you can edit on a small logical canvas but export at native resolution (composition size × scale). Only applies on the disableExportModal immediate-render path. See Composition Dimensions |
baseUrl | string | - | Optional API base for editor operations |
| Export Lifecycle | |||
onExportStart | (params: ExportStartParams) => void | - | Called when an export starts. For browser exports, params.estimatedSizeInBytes gives the estimated file size |
onExportProgress | (params: ExportProgressParams) => void | - | Called for progress changes and periodic heartbeats until the export ends |
onExportComplete | (params: ExportCompleteParams) => void | - | Called when an export completes |
onExportFail | (params: ExportFailParams) => void | - | Called when an export fails or times out |
onExportCancel | (params: ExportCancelParams) => void | - | Called when the user cancels an export. A cancellation does not call onExportFail |
exportProgressIntervalMs | number | 1000 | Time in milliseconds between progress heartbeats. Set a value of 0 or less to stop heartbeats |
webRenderStallTimeoutMs | number | 300000 | Maximum visible-tab time in milliseconds without browser render progress. Set 0 to stop this timeout |
webRenderFinalizationTimeoutMs | number | 1800000 | Maximum visible-tab time in milliseconds for each browser finalization phase |
| Media Adaptors | |||
adaptors | Adaptors | - | Pluggable sources for content: video, images, audio, text, templates, animations, lottie, transcription, and localMedia. If you omit a media type, the editor keeps its default stock adaptors for that type. If you set templates or lottie, your array replaces the built-in adaptors |
mediaLibrary | MediaLibraryConfig | - | Configure the in-panel media library tabs (Stock / My Library): which tabs show, their order, and their labels. Omit to derive tabs from configured adaptors. See Configuring the library tabs |
featureOverrides | FeaturesConfig | - | Add badges, upgrade prompts, or usage tracking to item actions. See Feature Overrides |
| Timeline | |||
thumbnailInterval | number | Adaptive | Seconds between generated timeline thumbnails. Smaller values add detail but use more processing and cache |
| 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 | |||
disableVideoKeyframes | boolean | false | Has no effect in the current version. The editor keeps the prop for compatibility |
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 | false | Watermark on the player and on exports. true for the default RVE badge, { src: '...' } for a 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. - The timeline opens with 4 track rows or fewer. If a project has more tracks, the tracks scroll inside the timeline panel. You can drag the panel taller.
Timeline Thumbnails
Configure generated thumbnails
Use thumbnailInterval to set the number of seconds between generated timeline thumbnails. A smaller value gives a more detailed timeline and uses more processing and cache space. If you omit the prop, the editor chooses an adaptive interval based on the timeline zoom and clip length.
<ReactVideoEditor projectId="my-project" thumbnailInterval={10} />Changing the prop causes the editor to generate and cache a sprite for the new interval. An invalid value, 0, or NaN restores adaptive interval selection.
Use external preview thumbnails
Set previewThumbnails on a video item—not on ReactVideoEditor—when your video service supplies a thumbnail sprite and a WebVTT file. VTT cues must use #xywh=x,y,width,height Media Fragment rectangles.
const videoItem: VideoItem = {
src: 'https://video.gumlet.io/.../video.mp4',
previewThumbnails: {
sprite: 'https://video.gumlet.io/.../preview_thumbnails.png',
vtt: 'https://video.gumlet.io/.../preview_thumbnails.vtt',
},
};The timeline fits the supplied sprite thumbnails to its cells and ignores cues outside the sprite image. It uses the supplied files without downloading the video to generate thumbnails.
No generated fallback
If the VTT cannot load or has no valid cues, the timeline shows its thumbnail error state. It does not download the video to generate thumbnails as a fallback.
Export Lifecycle
Use the export callbacks to show status in your host application or to monitor an export.
<ReactVideoEditor
projectId="my-project"
onExportProgress={({ phase, progress, heartbeat, renderedFrames, encodedFrames }) => {
console.log({ phase, progress, heartbeat, renderedFrames, encodedFrames });
}}
onExportCancel={({ compositionId }) => {
console.log('Export cancelled', compositionId);
}}
onExportFail={({ error }) => {
console.error('Export failed', error);
}}
/>onExportProgress receives these fields:
| Field | Type | Description |
|---|---|---|
phase | 'preparing' | 'rendering' | 'muxing' | 'storing' | Current export phase. The UI shows muxing and storing as finishing |
progress | number | Export progress from 0 to 1 |
renderedFrames | number | undefined | Number of rendered frames. Available for browser exports |
encodedFrames | number | undefined | Number of encoded frames. Available for browser exports |
totalFrames | number | Total number of frames in the export |
timestamp | number | Unix timestamp in milliseconds for this update |
heartbeat | boolean | true when the update has no new renderer progress |
compositionId | string | Project or composition ID |
durationInSeconds | number | Export duration in seconds |
renderType | 'ssr' | 'lambda' | 'browser' | 'custom' | Active render type |
Browser exports have progress and finalization watchdogs. The progress watchdog restarts when the renderer moves forward. Each of the muxing and storing phases gets a separate finalization deadline. The watchdog deadlines pause when the document is hidden.
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. The same prop also sets the quality of browser exports.
<ReactVideoEditor
projectId="my-project"
exportOptions={{
availableResolutions: ['720p', '1080p', '4k'],
defaultResolution: '1080p',
quality: 'high',
}}
/>| 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 |
quality | 'very-low' | 'low' | 'medium' | 'high' | 'very-high' | 'high' | Bitrate preset for browser exports. A higher preset gives a larger file |
videoBitrate | number | - | Video bitrate in bits per second. Overrides quality for the video track |
audioBitrate | number | - | Audio bitrate in bits per second. Overrides quality for the audio track |
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.
Export Quality and File Size
The quality, videoBitrate, and audioBitrate options apply to browser exports only. Cloud renders (SSR, Lambda, or RVE Cloud Rendering) use their own bitrate.
The default preset is 'high'. It encodes 1080p video at 6 Mbps. Earlier versions always used 'very-high' (12 Mbps), so the files were twice as large. To keep the earlier file size, set quality: 'very-high'.
quality | Video at 720p | Video at 1080p | Video at 4K | Audio |
|---|---|---|---|---|
'very-low' | ~0.4 Mbps | 0.9 Mbps | ~3.4 Mbps | 96 kbps |
'low' | ~0.8 Mbps | 1.8 Mbps | ~6.7 Mbps | 96 kbps |
'medium' | ~1.4 Mbps | 3 Mbps | ~11 Mbps | 128 kbps |
'high' | ~2.8 Mbps | 6 Mbps | ~22 Mbps | 192 kbps |
'very-high' | ~5.6 Mbps | 12 Mbps | ~45 Mbps | 192 kbps |
For an exact bitrate, set videoBitrate and audioBitrate in bits per second:
<ReactVideoEditor
projectId="my-project"
exportOptions={{ videoBitrate: 2_500_000, audioBitrate: 128_000 }}
/>The export dialog footer shows the estimated file size for the selected resolution. onExportStart gives the estimate as estimatedSizeInBytes, and onExportComplete gives the real sizeInBytes. The encoder uses a variable bitrate. Thus simple content, such as slides or screen recordings, is usually smaller than the estimate.
If the quality value is not valid, or a bitrate is not a positive number, the export fails before it starts.
To show the estimate in your own UI, use estimateExportSize. Give it the encoded size of the export, not the canvas size:
import {
estimateExportSize,
getEncodedExportDimensions,
} from '@reactvideoeditor/react-video-editor/types';
const { width, height } = getEncodedExportDimensions({ width: 1280, height: 720 }, '1080p');
const bytes = estimateExportSize({ width, height, durationInSeconds: 60, quality: 'medium' });The /types entry point also exports these helpers and types:
resolveExportBitrates(width, height, options)returns the{ videoBitrate, audioBitrate }that a browser export of that size uses.DEFAULT_EXPORT_QUALITYis'high'.- The types are
ExportOptions,ExportResolution,ExportQuality,ExportBitrateOptions,ExportBitrates, andEstimateExportSizeParams.
Composition Dimensions
Set the canvas to an exact pixel size at any ratio via editorState.dimensions. The same size is used in both preview and export.
<ReactVideoEditor
projectId="my-project"
editorState={{ tracks, dimensions: { width: 593, height: 4395 } }}
disableExportModal
/>How it works:
- Size is the source of truth. You pass
dimensions; the aspect-ratio label is derived from it. The ratio dropdown shows a matching preset (16:9, 1:1, …) when one fits, otherwise the raw dimensions (e.g.594 × 4396). Picking a preset just sets a new size and reframes items. - Omit
dimensionsand the canvas defaults to 16:9. - Export at the exact size. With a custom size, the export dialog adds an "Original" option (the composition size) and selects it by default. It's hidden when a preset already produces that size, so standard sizes like 1920×1080 don't show a duplicate row.
disableExportModalskips the dialog entirely — the Render button exports immediately at "Original" (the composition size), no scaling. Use it when the canvas size is the output size.defaultExportScalelets that immediate-render path export larger than the canvas. The Render button renders at composition size × the scale, so you can edit on a small logical canvas but export at native resolution (e.g. preview 1332×720 withdefaultExportScale={2.88}gives ~3840×2076). It only applies together withdisableExportModal— the resolution dialog has its own presets and ignores it, so setting the prop withoutdisableExportModalis a no-op.- Persistence.
dimensionsis saved inEditorState, so custom sizes survive reload. Pass the object back to restore.
<ReactVideoEditor
projectId="my-project"
editorState={{ tracks, dimensions: { width: 1332, height: 720 } }}
disableExportModal
defaultExportScale={2.88}
/>SSR / Lambda renders
For cloud rendering, your app's Remotion <Composition> must read width/height from inputProps for arbitrary sizes to reach the render. Client-side (WebCodecs) export uses the composition size directly.
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[]; // See Tracks and Items
dimensions?: { width: number; height: number }; // Canvas size at any ratio. Omit for the 16:9 default (1280×720). See Composition Dimensions
aspectRatio?: AspectRatio; // Deprecated — prefer `dimensions`. '16:9' | '1:1' | '4:5' | '9:16' | 'custom'
background?: CanvasBackground; // Composition background. It is not a timeline item
backgroundColor?: string; // Deprecated — prefer `background`. Saved output still includes it
playbackRate: number;
savedAt?: number; // Unix-ms timestamp, set automatically on every save
}
type CanvasBackground =
| { type: 'color'; value: string }
| { type: 'gradient'; value: string }
| { type: 'image'; src: string; fit: 'cover' | 'contain'; position: string; blur: number; wallpaperId?: string }
| { type: 'css'; value: string }; // Keeps legacy CSS backgroundsFor the structure of tracks, see Tracks and Items.
aspectRatio is deprecated
aspectRatio is now optional and deprecated in favor of dimensions. State that sets only aspectRatio still works — the size is seeded from the preset — so no changes are required for existing consumers. See Composition Dimensions.
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');
// Result: { 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.
- Connect RVE Cloud Rendering through
customRendererfor production workloads. - Use self-managed SSR or Lambda only when your team must own the rendering infrastructure.
- Add media sources via
adaptors(e.g. Pexels for images/videos). - Learn the editing tools and shortcuts in Editing and Shortcuts.
- Customize themes and integrate your app's theme switching UX.