Guides

* [Getting started](/developers)
* [Projects and storage](/developers/projects-and-storage)
* [Media, fonts and models](/developers/media)
* [Save and load](/developers/save-and-load)
* [Theme, features and events](/developers/theming-and-events)
* [Driving the engine](/developers/engine)
* [Limits and support](/developers/limits-and-support)
* [Troubleshooting](/developers/troubleshooting)

More

* [Reference](/developers/reference/)
* [Everything as one text file](/developers/llms-full.txt)

# 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.

```tsx
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.

```tsx
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](/developers/engine).

This component counts exports.

```tsx
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.

---
Source: [https://vidstudio.app/developers/theming-and-events](https://vidstudio.app/developers/theming-and-events)
