Video Studio SDKv0.0.3
UI

Color system

Theme tokens, light/dark modes, per-instance overrides, Tailwind utilities, and the programmatic API.

The SDK's color system is a layered token model on top of CSS custom properties. Every surface in the editor reads from a named token; you override the token, every dependent surface updates.

Three layers

  1. Studio primitives — a 12-step warm-neutral gray scale (--studio-gray-100--studio-gray-950) plus a violet accent set. Raw RGB channel triplets (e.g. "124 58 237") so they compose with alpha utilities.
  2. Studio semantic tokens — derived from the primitives. Surfaces (--studio-surface-ground, …-raised, …-overlay, …-float), borders (-subtle/-default/-strong), text (-primary/-secondary/-tertiary/-disabled), timeline (--studio-timeline-*), track type colors (--studio-track-video, …-audio, etc.).
  3. Shadcn-style semantic tokens--background, --foreground, --primary, --card, etc. These are computed from the studio tokens and consumed by the UI primitives (Button, Dialog, etc.).

The flow is primitives → studio semantic → shadcn semantic → components. Override at any level and everything downstream updates.

Light vs dark

The .vsdk-root element holds the light-theme tokens. Adding .dark switches them to the dark palette. Both are defined in @studio-dev/vsdk/styles.css. The provider toggles the class for you based on the theme prop and prefers-color-scheme.

<VideoStudioProvider theme="light">    {/* always light */}
<VideoStudioProvider theme="dark">     {/* always dark */}
<VideoStudioProvider theme="system">   {/* default — follow OS */}

You can read the resolved theme via the data-theme attribute on the root: data-theme="light" or data-theme="dark".

Overriding tokens

From the provider

themeOverrides accepts either a flat map (applies to both modes) or a per-mode object.

// Flat — same value in light and dark
<VideoStudioProvider
  themeOverrides={{
    primary:           '124 58 237',
    studioAccent:      '124 58 237',
    studioAccentHover: '109 40 217',
  }}
>

// Per-mode
<VideoStudioProvider
  themeOverrides={{
    light: { primary: '30 80 50',    studioAppBg: '245 245 245' },
    dark:  { primary: '155 120 255', studioAppBg: '20 18 28' },
  }}
>

Values are raw RGB channel strings ("R G B" with spaces, no rgb() wrapper). This is what lets Tailwind utilities like bg-studio-accent/30 apply alpha correctly.

Programmatically (anywhere)

import { applyThemeTokensToElement, createThemeTokens } from '@studio-dev/vsdk'

const tokens = createThemeTokens({
  primary: '124 58 237',
  studioAccent: '124 58 237',
})

const root = document.querySelector('.vsdk-root') as HTMLElement
applyThemeTokensToElement(root, tokens)

applyThemeTokensToElement writes the values as inline style properties on the element, which take precedence over the stylesheet. Any unknown keys are emitted under the --vsdk- prefix.

Per-mode programmatic resolution

resolveThemeOverrides returns the correct token bag for the current mode:

import { resolveThemeOverrides, type ThemeOverrides } from '@studio-dev/vsdk'

const overrides: ThemeOverrides = {
  light: { primary: '30 80 50' },
  dark:  { primary: '155 120 255' },
}
const tokens = resolveThemeOverrides(overrides, 'dark')   // { primary: '155 120 255' }

The provider does this internally — exposed here for advanced use (e.g. live theme editors).

Full token map

Shadcn semantic (RGB channels unless noted)

TokenPurpose
backgroundPage background
foregroundDefault text color
primaryBrand color (buttons, links, ring)
primaryForegroundText on primary surfaces
secondarySecondary surface
secondaryForegroundText on secondary
mutedMuted surface (input bg, tag bg)
mutedForegroundMuted text
accentAccent surface (hover states)
accentForegroundText on accent
borderDefault border
inputInput field background
ringFocus ring
radiusRoot radius (CSS length, e.g. "0.5rem")

Studio gray palette (RGB channels)

studioGray100 (lightest in dark mode / darkest in light mode) through studioGray950. Twelve steps. The semantic surface/border/text tokens are derived from these.

Studio surface hierarchy

TokenDefault mapping
studioAppBg--studio-gray-950 — outermost app background
studioSurfaceGround--studio-gray-900 — main editor surface
studioSurfaceRaised--studio-gray-850 — panels, cards
studioSurfaceOverlay--studio-gray-800 — popovers, dropdowns
studioSurfaceFloat--studio-gray-750 — floating layers (toasts, drag previews)

Studio borders / text

TokenUse
studioBorderSubtleDefault divider
studioBorderDefaultDefault border
studioBorderStrongHeavy border / outline
studioTextPrimaryMain text
studioTextSecondaryLighter body text
studioTextTertiaryHint, helper
studioTextDisabledDisabled state

Studio accent

TokenPurpose
studioAccentThe editor's accent (violet by default)
studioAccentHoverHover state
studioAccentSubtleSoft accent surface
studioSelectionColorTransform-handle and selection outline color

Track type colors

Each timeline track type has its own color:

TokenTrack
studioTrackVideoVideo items
studioTrackAudioAudio items
studioTrackTextText items
studioTrackImageImage items
studioTrackOverlayOverlay items
studioTrackShapeShape items
studioTrackCaptionsCaptions
studioTrackEffectEffects
studioTrackAdjustmentAdjustment layers

Timeline

TokenUse
studioTimelineBgTimeline panel background
studioTimelineRulerBgRuler strip
studioTimelineRulerTextRuler labels
studioTimelineTrackBorderBorder between tracks
studioTimelinePlayheadPlayhead line (red by default)
studioTimelineSnapSnap guide line (amber by default)

Inspector / player

TokenUse
studioInspectorInputBgInput background in the inspector
studioInspectorLabelLabel color in the inspector
studioPlayerBgPlayer letterbox background

Using tokens from your own code

As Tailwind utilities

The SDK registers a Tailwind v4 @theme inline block that exposes the studio tokens as utilities. You can use them directly in className strings:

<div className="bg-studio-surface-raised text-studio-text-primary border border-studio-border-subtle rounded-lg">
  ...
</div>

<span className="text-studio-track-audio">Audio clip</span>
<div className="bg-studio-accent/30">Soft accent surface</div>

Available utility prefixes: bg-studio-{app-bg,surface-ground,surface-raised,surface-overlay,surface-float}, bg-studio-{accent,track-*,timeline-playhead,timeline-snap,player-bg}, text-studio-{text-primary,text-secondary,text-tertiary,text-disabled,inspector-label}, border-studio-{border-subtle,border-default,border-strong}.

As raw CSS variables

.my-panel {
  background: rgb(var(--studio-surface-raised));
  color: rgb(var(--studio-text-primary));
  border: 1px solid rgb(var(--studio-border-subtle) / 0.6);
}

Because the values are raw RGB channel triplets, you can compose alpha with rgb(... / 0.6) syntax.

As shadcn semantic utilities

Standard shadcn utility names work because the SDK maps them in the same @theme inline block:

<div className="bg-card text-card-foreground border-border">…</div>
<button className="bg-primary text-primary-foreground hover:bg-primary/90">Save</button>

Motion & layout tokens

The theme stylesheet also defines a few non-color tokens you may want when matching the editor's feel:

TokenDefault
--studio-duration-fast100ms
--studio-duration-medium200ms
--studio-duration-slow300ms
--studio-ease-outcubic-bezier(0.16, 1, 0.3, 1)
--studio-ease-in-outcubic-bezier(0.4, 0, 0.2, 1)
--studio-z-base / -overlay / -modal / -toast1 / 10 / 50 / 100
--studio-shadow-sm / -md / -lgmode-aware soft shadows
--studio-topbar-height48px
--studio-timeline-header-width192px

These aren't part of ThemeTokens (the type), but you can read them in CSS or set them via inline style.

Recipes

Match the editor's accent in your own UI

<button className="bg-studio-accent text-white hover:bg-studio-accent/90 rounded-md px-3 py-1.5">
  My button
</button>

Build a small "filter pill" using surface + accent tokens

<button className="bg-studio-surface-raised text-studio-text-secondary border border-studio-border-subtle px-2 py-1 rounded data-[active=true]:bg-studio-accent-subtle data-[active=true]:text-studio-text-primary">
  Filter
</button>

Brand the editor without forking the stylesheet

<VideoStudioProvider
  themeOverrides={{
    primary:           '14 165 233',  // sky-500
    studioAccent:      '14 165 233',
    studioAccentHover: '2 132 199',
    studioSelectionColor: '14 165 233',
  }}
>

Two lines and the editor's accent, focus ring, primary button, and selection color all shift together.

On this page