Theme Author Guide

Voxis themes are self-contained ES modules loaded from the user themes directory.

Security: theme modules are executable JavaScript trusted by the app. They are not a sandbox for untrusted code. Only use third-party themes after reviewing their theme.js.

A minimal theme contains:

my_theme/
├── theme.json
└── theme.js

Manifest

theme.json uses manifest version 2:

{
  "manifest_version": 2,
  "id": "pulsar",
  "name": "Pulsar",
  "description": "A pulsing circle that grows with audio level",
  "api_version": 1,
  "entry": "theme.js",
  "overlay_width": 172,
  "overlay_height": 36,
  "params": { "color": "#00ff88" }
}

Required fields are manifest_version, id, name, api_version, and entry. description, params, overlay_width, and overlay_height are optional. The folder name is authoritative: if it differs from the manifest id, the loader uses the folder name.

JavaScript contract

theme.js must export mount(container, api) and return an object with unmount():

export function mount(container, api) {
  const canvas = document.createElement("canvas");
  canvas.width = api.size.width;
  canvas.height = api.size.height;
  container.appendChild(canvas);
  const ctx = canvas.getContext("2d");

  const unsubscribe = api.onState((state) => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = api.params?.color ?? "#00ff88";
    ctx.beginPath();
    ctx.arc(canvas.width / 2, canvas.height / 2, 4 + state.audioLevel * 12, 0, Math.PI * 2);
    ctx.fill();
  });

  return {
    unmount() {
      unsubscribe();
      canvas.remove();
    },
  };
}

Writing a WebGL / Canvas-3D Theme

The example above uses the 2D Canvas API, which covers most themes. For a raymarched/shader-based look, theme.js can instead draw with WebGL — the contract doesn’t care how you render, as long as mount() returns { unmount() }.

The builtin metaballs, metaballs25d, metaballs3d, and lavalamp themes are TypeScript ports of a standalone visualizer built for this same contract: github.com/axelbaumlisto/metaballs-viz (MIT). That repo documents its own params schema per engine, a WebGL graceful-fallback pattern (try/catch around context creation and shader compile/link — on failure, remove the canvas and return a no-op { unmount() {} } instead of throwing), and a runnable demo. Cloning it and reading metaballs3d.js is the fastest way to see a complete worked WebGL example. Full detail and a minimal WebGL skeleton are in docs/THEMES.md.

Theme API

The Theme API version is 1 and includes:

ThemeState has:

{
  mode: "idle" | "recording" | "transcribing" | "error",
  audioLevel: number,
  spectrumBins: number[]
}

Runtime behavior