DOCS / UI REFERENCE
Components
Five primitives cover the render surface. All of them accept className, style, and the pointer props listed in Events & gestures.
<View>
The flexbox container — the div of VSReacT. Backgrounds, borders, rounded corners, shadows, opacity, scroll containers, and knob arcs are all drawn on Views.
<View className="flex-1 flex-row items-center gap-4 p-6
bg-zinc-900 rounded-xl border hover:bg-zinc-800">
{children}
</View><Text>
Draws strings and numbers. Font size, weight, family (font-mono), letter-spacing, line-height, alignment, and color come from classes or style. Text nodes measure themselves into the Yoga layout, so a Text sizes its container like you expect.
<Text className="text-2xl font-bold tracking-tight text-center">
{gainDb.toFixed(1)} dB
</Text>Text is not selectable by default — plugin UIs are controls, and stray drags shouldn't highlight labels. Opt a node in with selectable (or the select-text class): drag selects, double-click grabs a word, Ctrl/Cmd+C copies the visible text, Escape or clicking away clears. The highlight colour is themeable via the selectionColor style key. Selection is per-node (one Text at a time) and skips truncate/line-clamp text.
<Text selectable className="font-mono text-[11] text-zinc-400">
{presetUrl}
</Text><Image src>
Paints an image from a file path or a base64 data: URI (since 0.0.18), scaled to its layout box. Formats: PNG, JPEG, GIF, BMP via JUCE's built-ins, and WebP since 0.0.26 (lossy VP8, lossless VP8L, alpha — decoded by a vendored libwebp; animated WebP shows its primary frame only). Plugin editors are fixed-size, so export assets at the exact pixels you draw them at — a 1:1 blit always beats runtime rescaling. Since 0.0.23: objectFit ("contain" default, "cover" clips overflow, "fill" stretches — or the object-* classes) and tintColor, which fills the image's alpha with a solid colour — one white icon PNG, any accent.
<Image src={logoDataUri} className="w-24 h-24 rounded-full" />For large assets — a full-panel reference-art plate is easily a multi-megabyte data URI — intern the image once with registerImage(src) (since 0.0.28). It decodes natively, keeps the bitmap on the C++ side, and returns a short "img:N" handle to use as the src, so the bridge never carries the pixels again. Idempotent per source string, same shape as registerFont, and it degrades to returning the raw src on an older native side.
import { registerImage } from "@vsreact/core";
const plate = registerImage(assets["plate.webp"]); // once, at module scope
// then: <Image src={plate} style={{ objectFit: "fill" }} /><Svg> + <SvgPath> (since 0.0.24)
Native vector paths. <Svg viewBox> lays out like a View and scales its <SvgPath> children from viewBox space onto the frame; each path takes SVG d data (parsed once, cached), fill (SVG's default black, "none" to skip), stroke/strokeWidth (viewBox units — scales like SVG), strokeCap/strokeJoin, strokeDash, and fillRule. Web icon sets port directly — rename the attributes and paste the d:
<Svg viewBox="0 0 24 24" className="w-6 h-6">
<SvgPath d="M12 2 L2 7 v10 l10 5 10-5 V7 Z" fill="none" stroke="#C6F135" strokeWidth={2} strokeJoin="round" />
</Svg><TextInput>
A real, chrome-stripped juce::TextEditor positioned by Yoga — real caret, selection, and IME, while VSReacT paints the box, border, and focus ring around it.
| PROP | TYPE | NOTES |
|---|---|---|
value / defaultValue | string | Controlled or uncontrolled, exactly like the DOM. |
placeholder | string | Color via the placeholderColor style key. |
disabled | boolean | Blocks focus and input. |
onChange / onSubmit | (value: string) => void | Submit fires on Enter. |
onFocus / onBlur | () => void | Pair with focus: class variants for focus rings. |
<TextInput
placeholder="Paste a link…"
className="w-full p-3 rounded-lg bg-zinc-900 border
focus:border-lime-400"
style={{ caretColor: "#C6F135", placeholderColor: "#6b7280" }}
onSubmit={(url) => native.call("download:start", { url })}
/>Built-in controls
Beyond the primitives, the SDK ships the classic VST controls, natively painted and drag-driven. Each takes a normalized 0..1 value (or an index) plus color/size props, and each has a Param* twin bound to a host parameter — see Audio parameters.
| CONTROL | FOR | NOTES |
|---|---|---|
<Knob> | Continuous values | Natively painted arc, vertical drag, wheel nudge, double-click reset to defaultValue; bipolar sweeps from 12 o’clock for pan-style params. variant picks a face from the component gallery: arc (default), instrument, gauge, metal, steel, glass, chickenhead, neon, blueprint — same gestures on every face. |
<Slider> | Continuous values | Horizontal by default; vertical makes it a fader (drag up for more, fill rises from the bottom). |
<Toggle> | Bypass, on/off, A/B | Spring-animated thumb; click to flip. |
<XYPad> | Two values at once | 2D drag pad with crosshair — cutoff/resonance, pan/depth. y = 1 is the top. |
<Segmented> | Exclusive choices | A row of options — oscillator shapes, filter modes. |
<Select> | Long choice lists | A dropdown — the menu renders in the overlay layer, positioned under the trigger via onLayout, scrolls past maxMenuHeight, click-away closes. |
<Meter> | Levels | Hot zone + peak-hold line with decay; feed it 0..1 values (typically pushed from C++ via useNativeEvent). |
<GenericEditor> | Whole plugins | One knob per APVTS parameter — a complete UI in one line. |
<MacroPad> | Centerpiece macros | A circular 2D pad with value-reactive concentric rings — x spreads them, y drives intensity. ParamMacroPad drives two host parameters from one drag. |
<HardwareKnob> | Hardware feel | Skeuomorphic cap with a glowing pointer notch at the rim and a faint tick track; same DAW gestures as Knob. |
<Crossfader> | Mix controls | The DRY/WET strip — wide track, grippy rectangular handle, double-click recenters. |
<PulseOrb> | Presence | A value-reactive orb: glowing core with echo rings that emit faster and brighter as the level rises. |
<NumberBox> | Fine values | The draggable number — BPM, ms, semitones. Drag, wheel-step, double-click reset; ParamNumberBox shows the host’s formatted text. |
<Checkbox> / <RadioGroup> | Settings panels | Checkbox rows and vertical exclusive options; Param twins map bool and choice parameters. |
<ProgressBar> / <Spinner> | Feedback | Determinate progress with optional percent (or an indeterminate sweep); indeterminate spinner painted with the native arc keys. |
<PianoKeyboard> | Performance | The playable keyboard — note-on on press, note-off on release, glissando by dragging across keys; heldNotes paints host MIDI in. |
<StepSequencer> | Patterns | The rows × steps grid: click cells on and off, downbeat tinting, a playhead column the host drives. Fully controlled. |
<ADSREnvelope> | Envelopes | The four-corner envelope editor — drag the attack peak, the decay/sustain corner, and the release corner; ParamADSREnvelope drives four host parameters. |
<PitchBend> / <ModWheel> | Wheels | The performance wheels: pitch springs back to center on release, mod stays where you leave it; Param twins for both. |
<Tabs> / <Disclosure> | Structure | The page switcher with an underline tab bar, and the collapsible settings row — both controlled or uncontrolled. |
<EQCurve> | Filters | The real summed biquad response (RBJ cookbook) with a draggable node per band — drag for freq/gain, wheel for Q. Pure math exported. |
<RingMeter> | Levels | A circular level meter on the native arc keys: hot zone, optional center readout via format. |
<Button> | Actions | solid / outline / ghost variants, three sizes, hover/active baked in. |
<Bars> | Spectra | Bottom-anchored bar visualizer with a hot zone — one bar per array entry. |
<Waveform> | Waveforms & envelopes | Centre-mirrored bars; pair with useRollingBuffer for scrolling history. |
<Tooltip> | Hover help | Wraps any child; shows the tip below it after a hover dwell (delayMs), via the overlay layer. |
<Modal> | Dialogs | Centered panel over a click-away backdrop — confirms, settings, about boxes. |
<Toggle on={bypassed} label="BYPASS" onChange={setBypassed} />
<Slider vertical height={140} value={mix} onChange={setMix} />
<XYPad x={cutoff} y={resonance} onChange={(x, y) => { setCutoff(x); setResonance(y); }} />
<Segmented options={["SINE", "SAW", "SQR"]} index={shape} onChange={setShape} />
function OutputMeter() {
const [level, setLevel] = useState(0);
useNativeEvent("meter", (m) => setLevel(m.level));
return <Meter value={level} length={140} label="OUT" />;
}
<Tooltip label="Double-click resets to 0 dB">
<ParamKnob paramId="gain" />
</Tooltip>
<Modal open={showAbout} onClose={() => setShowAbout(false)} title="ABOUT">
<Text className="text-[12] text-zinc-400">MyPlugin 1.0 — built with VSReacT.</Text>
</Modal><NativeView nativeId>
The escape hatch: mounts any juce::Component you registered in the NativeRegistry inside the React layout. React owns its position and size; JUCE owns its painting. Perfect for waveform displays, meters fed from the audio thread, or any legacy component you are not ready to rewrite.
// C++ — register a factory
vsreact::NativeRegistry registry;
registry.registerFactory ("waveform", [this] {
return std::make_unique<WaveformDisplay> (processor);
});
// TSX — position it with flexbox
<NativeView nativeId="waveform" className="flex-1 rounded-lg overflow-hidden" /><Canvas> (since 0.0.27)
The raster escape hatch: draw(pixels, width, height) writes RGBA into an ArrayBuffer that aliases the node's C++-owned pixel store — a genuine binary channel, so per-pixel drawing never crosses the JSON bridge. The node is sized by className/style like any View; width/height are the pixel-buffer dimensions (set them to CSS size × device-pixel-ratio for a crisp result). draw re-runs when deps (or the buffer size) change.
<Canvas
className="w-24 h-24"
width={96} height={96}
deps={[value]}
draw={(pixels, w, h) => {
for (let i = 0; i < w * h; i++) {
pixels[i * 4] = 255; // R
pixels[i * 4 + 3] = 255; // A
}
}}
/>Know the ceiling: QuickJS is too slow for per-frame procedural shading — one full 180×180 Blinn-Phong knob measured ~217 ms per frame. <Canvas> suits modest procedural drawing that redraws occasionally; anything shader-like should be baked at build time and shipped as an image — which is exactly what the film-strip tooling below does.
Film-strip knobs (since 0.0.27)
The commercial-plugin answer to the canvas perf ceiling: bakeKnobStrip (a build-time tool in the SDK's src/tools/) renders a pure knob shader to N rotation frames stacked vertically in one lossless-WebP sprite sheet plus a JSON manifest. At runtime, FilmStripKnob (exported from @vsreact/core) shows one frame via Image + overflow: hidden + translateY — no runtime shading, and the big strip URI crosses the bridge once, not per frame.
# build time — writes knob-strip.webp + knob-strip.json
bun run vsreact/js/src/tools/bakeKnobStrip.ts <outDir> [size] [frames]
// runtime — rotation in degrees within ±sweepDegrees/2
<FilmStripKnob rotation={-135 + 270 * value} strip={knobStrip} displaySize={72} />