Theme, features and events
The editor is the VidstudioEditor component from @vidstudio/editor. Three props shape it: theme, features and shortcuts. One prop, onEvent, reports what happens inside it. mountEditor takes the same props.
The theme
A theme is an object of type EditorTheme. Each key sets one custom property on the editor root element. The package exports two full themes: darkTheme (the default) and lightTheme.
Pass a partial theme to change some keys only. The other keys keep their default.
import { VidstudioEditor, lightTheme } from "@vidstudio/editor";
import type { VidstudioEditorProps } from "@vidstudio/editor";
export function BrandedEditor(props: Pick<VidstudioEditorProps, "store" | "projectId">) {
return (
<div style={{ height: "100vh" }}>
<VidstudioEditor {...props} theme={{ ...lightTheme, accentPrimary: "#0f766e", fontFamily: "Inter, sans-serif" }} />
</div>
);
}
The host page is not changed. The variables live on the editor root, not on :root.
Four keys need an explanation. textOnAccent colours text and icons on an accent button. accentWarning also colours keyframes. mediaBg, mediaText and mediaTextMuted sit behind the video and over it. They stay dark and light in both themes.
| Key | darkTheme | lightTheme |
|---|---|---|
bgPrimary |
#0a0a0b |
#ffffff |
bgSecondary |
#111113 |
#f6f6f7 |
bgTertiary |
#18181b |
#eeeef0 |
bgCard |
#141416 |
#fbfbfc |
bgCardHover |
#1c1c1f |
#f1f1f3 |
bgElevated |
#202024 |
#ffffff |
textPrimary |
#ffffff |
#18181b |
textSecondary |
#a1a1aa |
#52525b |
textTertiary |
#71717a |
#71717a |
textMuted |
#52525b |
#a1a1aa |
textOnAccent |
#ffffff |
#ffffff |
accentPrimary |
#ff6363 |
#e5484d |
accentPrimaryHover |
#ff7a7a |
#d63c41 |
accentSecondary |
#3b82f6 |
#2563eb |
accentSuccess |
#22c55e |
#16a34a |
accentWarning |
#f59e0b |
#d97706 |
accentWarningHover |
#fbbf24 |
#b45309 |
accentError |
#ef4444 |
#dc2626 |
borderPrimary |
#27272a |
#e4e4e7 |
borderSecondary |
#3f3f46 |
#d4d4d8 |
borderHover |
#52525b |
#a1a1aa |
errorSurface |
#3a1a1a |
#fef2f2 |
errorBorder |
#662222 |
#fecaca |
errorText |
#ff8888 |
#b91c1c |
warningSurface |
#3a2e1a |
#fffbeb |
warningBorder |
#665522 |
#fde68a |
warningText |
#f0b45a |
#b45309 |
mediaBg |
#000000 |
#000000 |
mediaText |
#ffffff |
#ffffff |
mediaTextMuted |
#cccccc |
#cccccc |
timelineBg |
#111111 |
#fafafa |
timelineTrackBorder |
#2a2a2a |
#e4e4e7 |
timelineRulerText |
#888888 |
#71717a |
timelinePlayhead |
#e8524a |
#e5484d |
timelineWaveform |
#f4845f |
#ea580c |
timelineSnap |
#4fc3f7 |
#0284c7 |
radiusSm |
6px |
6px |
radiusMd |
8px |
8px |
fontFamily |
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Inter', 'Helvetica Neue', Arial, sans-serif |
same as dark |
Feature flags
The features prop is a partial object of type EditorFeatures. Every flag defaults to true. The full set is exported as allFeatures.
This mounts an editor with captions and the tour off.
import { VidstudioEditor } from "@vidstudio/editor";
import type { VidstudioEditorProps } from "@vidstudio/editor";
export function PlainEditor(props: Pick<VidstudioEditorProps, "store" | "projectId">) {
return (
<div style={{ height: "100vh" }}>
<VidstudioEditor {...props} features={{ captions: false, tour: false }} />
</div>
);
}
| Flag | What disappears when it is off |
|---|---|
sourceMonitor |
The Source and Program tabs over the preview, and the Source Monitor panel. |
tour |
The tour button and the tour that opens on the first visit. |
safeZones |
The guides picker over the preview, and the guide lines it draws. |
captions |
The Transcribe control in the effect panel. No speech model is ever downloaded. |
transitions |
The transition handles where two clips meet on a video or audio track. |
keyframes |
The keyframe markers on timeline clips, and the keyframe controls in the effect panel. |
export |
The Export button and the export dialog. |
projectSettings |
The canvas size picker and the frame rate picker. |
textTracks |
The "Add text track" button in the timeline toolbar. |
imageImport |
The image import button in the timeline toolbar. |
audioImport |
The audio import button in the timeline toolbar. |
shortcutsHelp |
The shortcuts button, and the key that opens the shortcut list. |
projectFiles |
The "Save project" and "Open project" entries of the project menu. |
Shortcuts
The shortcuts prop has two values.
| Value | Where keys are heard |
|---|---|
"scoped" (default) |
On the editor root only. A key reaches the editor only while focus is inside it. Inputs on the host page are never affected. |
"global" |
On the window. Every key on the page reaches the editor. Use it for a page that holds nothing but the editor. |
Events
onEvent receives two kinds of event. An engine event comes from the backend; its type is EditorEvent from @vidstudio/engine. A user interface event comes from the editor's own components; its type is EditorUiEvent from @vidstudio/editor. Every event has a kind field. The other fields are the payload.
| Kind | Source | Payload | When it fires |
|---|---|---|---|
timeline-changed |
engine | none | A track, clip or transition changed. |
effects-changed |
engine | clipId: string | null |
An effect, keyframe or geometry changed on one clip. null means re-read every clip. |
project-changed |
engine | none | The name, canvas size, frame rate, work area or loop setting changed. |
player-state |
engine | state: "idle" | "loading" | "ready" | "playing" | "paused" | "seeking" |
The player changed state. |
time |
engine | seconds: number |
The playhead moved. |
duration |
engine | seconds: number |
The timeline length changed. |
load-progress |
engine | fraction: number |
The project is loading into the player, 0 to 1. |
proxy-progress |
engine | assetId: string, fraction: number | null |
A preview proxy is being made, 0 to 1. null means it finished, failed or stopped. |
audio-loading |
engine | count: number |
How many audio tracks are still decoding their sound. |
export-progress |
engine | progress: { phase, currentFrame, totalFrames, percent } |
The export advanced. phase is "preparing", "video", "audio" or "finalizing". |
warning |
engine | message: string |
The engine has something to show the user. |
error |
engine | message: string, error: Error |
An import or playback failure. |
preview-played |
engine | none | play() started the preview. Sent on every play. |
media-imported |
engine | mediaType: "video" | "audio" | "both", clipDurationSec: number, addedTo: "new_track" | "existing_track" |
An asset was placed on the timeline with addTrackFromAsset or addClipFromAsset. A picture with its linked sound is one event, "both". |
export-started |
engine | settings: ExportSettings |
An export began. |
export-finished |
engine | settings: ExportSettings, result: ExportResult, durationMs: number |
An export produced a file. |
export-failed |
engine | settings: ExportSettings, error: Error, phase: string | null, durationMs: number |
An export failed for any reason but a cancel. phase is the last progress phase reached. |
storage-full |
engine | operation: "import" | "export" | "delete_track", error: Error |
Browser storage refused a write because it is full. |
export-downloaded |
user interface | settings: ExportSettings |
The user pressed Download in the export dialog, or onExport received the file. |
export-released |
user interface | reason: "dialog_closed" | "page_left", outputBytes: number |
The finished export file was deleted from browser storage. |
ExportSettings and ExportResult are described in Driving the engine.
This component counts exports.
import { useState } from "react";
import { VidstudioEditor } from "@vidstudio/editor";
import type { VidstudioEditorProps } from "@vidstudio/editor";
export function CountingEditor(props: Pick<VidstudioEditorProps, "store" | "projectId">) {
const [finished, setFinished] = useState(0);
const [downloaded, setDownloaded] = useState(0);
return (
<div style={{ height: "100vh" }}>
<p>
{finished} exports finished, {downloaded} downloaded
</p>
<VidstudioEditor
{...props}
onEvent={(event) => {
if (event.kind === "export-finished") setFinished((n) => n + 1);
if (event.kind === "export-downloaded") setDownloaded((n) => n + 1);
}}
/>
</div>
);
}
A new onEvent function does not rebuild the editor. A new options object does, so keep options stable.