Editor quickstart
@videoflow/react-video-editor is a drop-in editor UI — timeline, preview, inspector, keyboard shortcuts, undo/redo. You feed it a VideoJSON and it hands back a new one on every change.
Minimal example
import { VideoEditor } from '@videoflow/react-video-editor';
import '@videoflow/react-video-editor/style.css';
const initialVideo = {
name: 'My project',
width: 1920, height: 1080, fps: 30, duration: 10,
layers: [],
};
export default function App() {
return (
<VideoEditor
video={initialVideo}
onChange={(next) => console.log('edited', next)}
onSave={(video) => saveToServer(video)}
onExportComplete={(blob) => downloadBlob(blob, 'out.mp4')}
theme="dark"
/>
);
}onExport undefined and rely on the built-in export modal + onExportComplete to receive the rendered Blob. Set onExport only when you want to skip the modal and run your own UI / pipeline; whatever it resolves to is forwarded to onExportComplete.Props
| Prop | Type | Notes |
|---|---|---|
video | VideoJSON | Initial state. Controlled re-renders supported. |
onChange | (video) => void | Fires on every edit. Debounced internally. |
onSave | (video) => void | Promise<void> | Fires on Cmd+S / titlebar Save. Setting it makes a Save button appear and the editor tracks an isSaved flag. |
onExport | () => Promise<unknown> | Optional custom export. When set, replaces the built-in modal; whatever it resolves to is forwarded to onExportComplete. |
onExportComplete | (video) => void | Receives the rendered Blob from the built-in modal (or the value onExport resolved to). When set with the built-in modal, the editor skips its auto-download. |
onUpload | (file) => Promise<string> | Resolve to a URL. See Uploads. |
theme | 'light' | 'grey' | 'dark' | 'night' | Defaults to 'dark'. See Theming. |
branding | ReactNode | null | false | Leading brand node for the titlebar (logo + wordmark). Omit / pass null to fall back to the project-name input. |
components | { Titlebar, Preview, Sidebar, Timeline } | Swap any panel. See Custom panels. |
Sizing
The editor fills its parent. Wrap it in a block with an explicit height — typically 100vh minus your app chrome.
<section style={{ height: 'calc(100vh - 64px)' }}>
<VideoEditor video={v} onChange={setV} theme="dark" />
</section>useEffect.