Syntho UI Kit
The controls a plugin interface is actually made of — knobs, faders, XY pads, footswitches, assignable CC strips. They draw themselves in SVG and inline styles rather than utility classes, so they carry no Tailwind config and no stylesheet of consequence, and the same components render in a web page and inside an exported VST3 without a second implementation.
Nothing here is a dependency you take on. The CLI copies the source into your project and you own it from then on.
Live demo
A whole front panel, composed from the kit
Everything below is interactive and wired to one piece of React state. Drag a knob, hold Shift for a fine pass, double-click to reset it. Drag a CC chip by its grip to reorder the strip. The parameter line along the bottom is the panel's live state, as an engine would receive it.
Sequence
Filter
Envelope
EQ
Delay
MIDI CC
map: Mod→Cutoff(200..6000 exp)cutoff=2200 reso=0.35 drive=0.28 pan=0.00 time=340 fb=0.42 tone=0.45 depth=0.60 out=0.0 bypass=0Install
One CLI command per control
The kit is published as a shadcn registry. Point your components.json at the @syntho-ui namespace once, then add controls by name. Each one arrives as source in your own tree — no package to version, no build step, and every dependency it needs comes along with it.
$ npx shadcn add @syntho-ui/knob$ npx shadcn add @syntho-ui/fader @syntho-ui/xy-padOne-time setup in components.json
{
"registries": {
"@syntho-ui": "https://engine.syntho.app/r/{name}.json"
}
}Controls
Everything in the registry
Each card is a live instance. Open one for its full source, props, and install command.
Controls
Drag or scroll to turn — a diagonal drag counts, so long as it isn't running across the knob's axis. Shift-drag is fine; Shift+arrow advances five steps. Double-click to reset.
Dual Band Selector
dual-band-selectorThe Logic-style Low Cut / High Cut band bar.
shadcn add @syntho-ui/dual-band-selectorTriangle Pad
triangle-padTwo normalized controls, three post-computed corner weights.
shadcn add @syntho-ui/triangle-padToggle Switch
toggle-switchRocker, stomp or hardware lever with a satisfying pressed state.
shadcn add @syntho-ui/toggle-switchHold the first, latch the second, or use the smaller console faces for vintage hardware panels. Space and Enter work too.
Glow Button
glow-buttonLit performance and console buttons whose glow can follow the music.
shadcn add @syntho-ui/glow-buttonTurn a knob, or drag a chip by its grip to reorder.
CC Knob Strip
cc-knob-stripDraggable MIDI CC chips you can turn and reorder.
shadcn add @syntho-ui/cc-knob-stripdefault — loose pieces, bring your own panel
frame + variant="compact" — the little black box
Drag a handle and the knobs follow; turn a knob and the shape follows. Both editors edit the same value.
Envelope Editor
envelope-editorThe envelope shape and its four knobs, kept in sync.
shadcn add @syntho-ui/envelope-editorDrag a handle for frequency and gain, wheel over one for Q, or tab to it and use the arrows — Alt+Left/Right (or [ and ]) for Q, Shift for a fine pass. In bars mode the columns stand at their own frequencies, so the curve behind them is the curve they make.
EQ Editor
eq-editorHands on the curve: N draggable bands, two readings.
shadcn add @syntho-ui/eq-editorVocoder Panel
vocoder-panelSpeech envelopes mapped onto a carrier, with the routing visible.
shadcn add @syntho-ui/vocoder-panelTap a cell to toggle it, drag across a row to paint a stroke, drag up or down on a lit cell to ride its velocity (Shift is fine). Velocity is fill height and brightness both; gate is the fill's width. The playhead is a prop — this demo owns the clock, the grid just draws it — and the page follows it.
Step Grid
step-gridThe sequencer grid: tap, paint, ride the velocity.
shadcn add @syntho-ui/step-gridE(3,8) — gaps 3-3-2. Euclid spreads the pulses as evenly as whole steps allow, which is why the gaps only ever take two values one step apart, and why the defaults here are E(3,8): the 3-3-2 tresillo, a rhythm that already existed long before Bjorklund wrote the algorithm down. Rotate and the polygon spins without changing shape — the pattern is a necklace. The playhead is a prop: this demo owns the clock and advances a fractional step position at 2 steps per second, so the hand sweeps smoothly between nodes and the highlight snaps to the nearest one.
Rhythm Wheel
rhythm-wheelA bar as a circle, and the shape the pattern makes.
shadcn add @syntho-ui/rhythm-wheelThe demo owns the clock — an interval integrating beats, which is why dragging the tempo speeds the transport up from where it is instead of rewriting where it has been. The component only ever receives that position as a prop and pulses its light from it, the same rule lfo-visualizer and step-grid follow. Drag the number to set the tempo (Shift for tenths, double-click to reset), or tap the key four times: tap tempo here is pure maths over a list of timestamps, median-averaged so one fumbled tap does not move it. Swing lives in the same lib, built and tested with no face passing it yet — the sequencer that wants it is still coming-soon, the same call step-pattern made about rotateSteps.
Transport Controls
transport-controlsPlay, stop, loop, a tempo you can drag, and a division that means something.
shadcn add @syntho-ui/transport-controlsPress a key: the velocity comes from how far down the key you land, front loudest, with a floor so an edge tap is quiet rather than silent. Drag along the keys to glissando — the previous note is released as the next is struck — or play from the QWERTY row (space panics, − and = shift the octave). This demo is a scale-locked instrument: strikes outside the scale are declined, and a declined key draws struck-but-silent rather than dark, because a key that produces nothing is otherwise indistinguishable from a broken one. No audio here: the kit emits notes, hosts make the sound.
Piano Keyboard
piano-keyboardThe playable surface: press, glissando, type.
shadcn add @syntho-ui/piano-keyboardThe selector owns the choice. The oscillator or LFO receives the value and remains the source of truth.
Waveform Selector
waveform-selectorA visual waveform picker in a compact popover.
shadcn add @syntho-ui/waveform-selectorViewers
Drag the peak, the knee or the tail. Handles are focusable — arrows step, Shift is finer.
Envelope Display
envelope-displayAn ADSR envelope you can draw on.
shadcn add @syntho-ui/envelope-displaySynthetic signal for display only — this demo produces no audio.
A synthesised programme through a modelled 4:1 compressor — the GR bar hangs from the top and holds its deepest recent bite.
Gain Reduction Meter
gain-reduction-meterCompressor metering: IN and OUT bars, GR hanging from the top.
shadcn add @syntho-ui/gain-reduction-meterFour bands, summed from their biquad coefficients — no analyser involved. The travelling dot is the cursor prop: a frequency in, a point on the real curve out, for modulated filters.
EQ Display
eq-displayFilter response curve, computed rather than measured.
shadcn add @syntho-ui/eq-displayThe shaded strip is the knee — the soft in soft-knee, drawn as a width rather than implied by a number. The meter is fed a level sweeping through the threshold, read through this same curve.
Dynamics Display
dynamics-displayTransfer curve for a compressor or a gate, plus its GR meter.
shadcn add @syntho-ui/dynamics-displayThe dashed diagonal is the signal untouched; the gap to the curve is the distortion, at true scale. The bright stretch is the part of the curve a swelling tone is exercising, measured from real samples.
Transfer Curve
transfer-curveWhat a waveshaper does to a sample, drawn.
shadcn add @syntho-ui/transfer-curve11 audible repeats above -60 dB, a 2.89 s tail. Height is level and so is opacity: by the fifth repeat a pip is two units tall, and one cue is not enough to read a tail by. The hollow pip at zero is the dry hit, whose own height is 1 - mix, because the stage crossfades rather than adding a send.
Delay Tap Display
delay-tap-displayA delay's echo train, drawn on a time axis.
shadcn add @syntho-ui/delay-tap-displaySegment Display
segment-displayThe LED readout off the front of a rack unit.
shadcn add @syntho-ui/segment-displayDot Matrix Display
dot-matrix-displayThe character screen bolted into the front of a rack unit.
shadcn add @syntho-ui/dot-matrix-displayPlaying: the head sweeps and the readout counts bar.beat.sixteenth. Stop it and the cursor and readout vanish — a parked playhead at zero reads as a bug, so it is simply not drawn. Click a note to select it; Shift-click to extend. Turn on Edit to move and stretch notes (experimental — no sequencer consumes edits yet). Every colour is a --uikit-* token, so the roll follows the preview plane's theme — the same channel a host that repaints per team uses.
Piano Roll
piano-rollThe whole loop as a pitch × beat note grid — rendered at once.
shadcn add @syntho-ui/piano-rollKick, snare and hats over one bar. Push the Swing slider and watch the offbeat hats slide to the right of their sixteenth line — the same movement a step-dot grid hides, because it snaps every hit to the middle of its cell. Each hit is a dot at its real onset; velocity sets its size, so the ghosted offbeat hats read as quieter. The head sweeps and lights whatever hit it is on. Same playhead, active-state and theming as the piano roll — a drum view is that roll with named lanes instead of pitches.
Drum Lane
drum-laneNamed lanes and hits drawn where they land, so swing is visible.
shadcn add @syntho-ui/drum-laneThe scope draws the grid, the dB gutter, the dashed budget line and the playhead; the cyan curve comes from the demo through the children render slot, using the same layout.xForBeat / layout.yForDb the grid was drawn with — which is why it cannot drift out of alignment with the onset ticks below. Push Swing and the ghosted offbeat percs slide off their sixteenth lines. Tick Host transport and the playhead switches to the stand-in DAW's ppqPosition; the caption follows the clock's reported source rather than claiming a sync it does not have. Stop, and the playhead disappears entirely.
Beat Grid Scope
beat-grid-scopeBars across, dB downward, and a slot where you draw the signal.
shadcn add @syntho-ui/beat-grid-scopeFour chips, four different claims. Audition and Host sync toggle between lit and off — the facility is there either way. Output tap is the one that matters: when the host publishes no tap it goes dim, not off, because "this host cannot tell me" is a different statement from "measurement is switched off", and a face that draws them the same has told you something false about your session. Source sits on warn: engaged, but not the source you asked for. Only live pulses.
Status Chip
status-chipFive states a face can tell apart, including the two that matter.
shadcn add @syntho-ui/status-chipUntick the tap and the first readout becomes an em dash with a note saying why — not 0.0 dB, which would be a measurement nobody made. A real zero still prints as zero; that negative case is pinned in the test, because confusing a measured 0.0 with "no reading" is the same bug wearing the other hat. Push the reduction past the budget and the value alone takes the warning accent, while the placeholder readout on the right never lights up at all. Figures are tabular, so the number does not reflow as its digits change.
Value Readout
value-readoutA big honest number: tabular, unitted, and a dash when there is nothing.
shadcn add @syntho-ui/value-readoutTwo knobs draw, one measures. Pan and Width are the parameter side — the solid cursor and the band around it are what was asked for, animated here by the demo's own interval the way an auto-panner would animate them from its LFO. The hollow diamond is the signal side: where the energy actually landed, which sits at tan(p·π/4) rather than at p, so at half right the knob says 0.50 and the meter says 0.41 and both are correct. Decorrelate slides the right channel's phase; the meter reads the cosine of it (+1.00 at 0°), and panning does not move it at all, because correlation is invariant to per-channel gain — until the very ends, where one channel goes silent and the meter drops to zero rather than claim a phase relationship with nothing. Past 90° the needle crosses into the red half, and that half is the one that matters: material below zero cancels itself the moment anything downstream folds to mono — a phone speaker, a club's sub feed — so at -1 the sound you are mixing simply is not there. For the shape of the image rather than its numbers, put a lissajous-scope beside this; the two are the same signal seen from opposite ends.
Stereo Field
stereo-fieldWhere the sound sits between the speakers, and whether it survives a mono sum.
shadcn add @syntho-ui/stereo-fieldReport
Runtime
Feed health
| Feed | Expected | Observed | Age | Dropped | Verdict |
|---|---|---|---|---|---|
| host | 30 Hz | 0 Hz | never | 0 | silent |
| transport | 30 Hz | 0 Hz | never | 0 | silent |
| midiIn | 30 Hz | 0 Hz | never | 0 | silent |
| midiOut | 30 Hz | 0 Hz | never | 0 | silent |
| meters | 30 Hz | 0 Hz | never | 0 | silent |
| scope | 15 Hz | 0 Hz | never | 0 | silent |
| lifecycle | 30 Hz | 0 Hz | 0 ms | 0 | starved |
| counters | 30 Hz | 0 Hz | never | 0 | silent |
| params | 30 Hz | 0 Hz | never | 0 | silent |
| presets | 30 Hz | 0 Hz | never | 0 | silent |
| clock | 30 Hz | 0 Hz | never | 0 | silent |
Identity
Feeds
Every tab on a scripted mock host — seeded, so these are the same frames on every visit. Values come from the snapshot or render as an explicit Unreported cell; nothing here is a default dressed up as a measurement. The Sampler tab appears only because this mock declares a bank table — it is capability-gated, so a synth never shows it. Overview leads with the status strip; Audio puts four viewers on one shared animation frame with a trigger and a single-shot freeze; Events filters by kind, severity and "since last preset load". Under 480 px the rail collapses to glyphs and keeps its labels for screen readers.
Diagnostic Panel
diagnostic-panelWhat the host told the plugin, what reached it, and what it produced — in eleven tabs.
shadcn add @syntho-ui/diagnostic-panelA module face
Right-click anywhere here, or press Ctrl/Cmd+Shift+D, to open the diagnostics modal, and Escape to close it. It stays centered in the viewport, moves focus into the rail on open, and returns it to the face on close. Click outside the panel to dismiss it.
Mounted with policy="always" so the docs page can show it; under the default "development" policy it appears only for a development build, the dev runner, or a face with no host at all.
Diagnostic Overlay
diagnostic-overlayRight-click a module face to open its diagnostics, and only where that is allowed.
shadcn add @syntho-ui/diagnostic-overlayVisualizers
Synthetic signal for display only — this demo produces no audio.
One mono signal against a 248° phase-shifted copy of itself. The trail decays instead of clearing, so the figure keeps a phosphor tail.
Lissajous Scope
lissajous-scopeA vectorscope figure from a single mono signal.
shadcn add @syntho-ui/lissajous-scopeKick and bass centred, hats ticking off to the side, a Haas-delayed pad and an uncorrelated reverb tail. The bright vertical spine is the low end; the cloud around it is the pad and the room. Measured width here is 20%.
A 96 BPM mixdown — kick, hats, a Haas-delayed pad and an uncorrelated reverb tail — synthesised a frame at a time and handed over through getStereoData, the same contract an engine tap satisfies, so the component cannot tell this from a live analyser pair. The page stays silent: nothing here builds an AudioContext. The 45° rotation is what makes the reading free — an unrotated X-Y plot puts mono on the diagonal, and lissajous-scope draws exactly that. Here upright is mono, so “is it standing up” and “does it survive a mono sum” are the same question. Turn Auto-gain off and the trace plots at the signal’s own amplitude; the CORR and WIDTH readouts do not move when you do, because they are taken from the raw pair and never from the scaled copy — a meter that moved with the display gain would be lying.Split L/R swaps the rotated diamond for the raw per-channel plot: X is left, Y is right, and mono moves from the vertical to the rising diagonal. It answers a different question — about the channels rather than the image — so a dead channel collapses the figure onto one axis and a level mismatch tilts the diagonal, both of which the diamond hides. For the same signal’s numbers rather than its shape, put a stereo-field beside this.
Goniometer
goniometerThe stereo image as a shape — mono straight up, phase problems flat.
shadcn add @syntho-ui/goniometerSynthetic spectrum for display only — this demo produces no audio.
Particles settling on the nodal lines, and the same modes rendered as a field on a circular plate. Drag the slider to retune the plate.
Chladni Plate
chladni-plateThe nodal figure sand draws on a driven plate.
shadcn add @syntho-ui/chladni-plateDrawn from the waveform's closed form, not measured — the cursor rides its own clock at the shown rate.
LFO Visualizer
lfo-visualizerAn LFO's waveform, with a cursor on the live phase.
shadcn add @syntho-ui/lfo-visualizerA C-major histogram on the wheel. The seven notes of the key form one unbroken arc, and the needle points at the tonal centre — sitting just off C, pulled by the relative minor.
Circle of Fifths
circle-of-fifthsA pitch-class histogram on the wheel, with a tonal-centre needle.
shadcn add @syntho-ui/circle-of-fifthsEvery major triad is the same triangle at a different rotation — step through the chords to watch it turn, and switch the ring order to see the same chord take a different shape.
Pitch Class Clock
pitch-class-clockChords as shapes on a twelve-note dial.
shadcn add @syntho-ui/pitch-class-clockThe five accidentals never sound, so they sit at the stub floor. The tick under C marks the tonic; the outlined bars are the chord playing now.
Pitch Class Histogram
pc-histogramTwelve bars: how much each pitch class sounded.
shadcn add @syntho-ui/pc-histogramI-V-vi-IV. The thick ties are the two-note relationships — C to Am, Am to F — which is why those swaps feel so smooth. G shares nothing with Am or F, so it sits unconnected to either.
Chord Graph
chord-graphHow the chords in a progression connect, by shared notes.
shadcn add @syntho-ui/chord-graphC and Cm draw the identical shape — they are inversions of each other, and the vector cannot tell them apart by design. The diminished seventh is all minor thirds and tritones; the whole-tone scale has no minor thirds or fourths at all.
Interval Vector Chart
interval-vector-chartThe harmonic fingerprint of a chord, in six bars.
shadcn add @syntho-ui/interval-vector-chartSlide to 0 and the row is the key alone — the same seven notes stay lit whatever you hold. Slide to 1 and it tracks only the chord. In between, the notes belonging to both glow brightest.
Consonance Matrix
consonance-matrixWhich of the twelve notes fit what you are playing.
shadcn add @syntho-ui/consonance-matrix100010010001[0158]Am/C is the inversion test — C is in the bass, but A is still the root, so it names as Am rather than a rootless C6. The diminished seventh is symmetric: all four rotations are equally valid names, and the alternatives say so.
Chord Readout
chord-readoutEverything analysis can say about what is sounding.
shadcn add @syntho-ui/chord-readoutSynthetic signal for display only — this demo produces no audio.
Foundations
| beat | xForBeat | piano roll x | yForDb(beat dB) |
|---|---|---|---|
| 0 | 34.00 | 34.00 | 8.00 |
| 1 | 68.75 | 68.75 | 30.33 |
| 2.5 | 120.88 | 120.88 | 63.83 |
| 3.75 | 164.31 | 164.31 | 91.75 |
| 8 | 312.00 | 312.00 | 142.00 |
The two x columns are the point: the beat grid's xForBeat and the piano roll's are the same numbers, because they are the same function — the grid builds on createLaneLayout rather than growing a second time axis, and the test asserts the agreement at every subdivision. The dB column runs the other way: 0 dB at the top of the plot, reduction downward, and the axis floor labelled −∞ because it is the bottom of what the plot can show rather than a value anyone measured.
Beat Grid Geometry
beat-grid-geometryThe bars-across, dB-down plot as pure maths — sharing the piano roll's x axis.
shadcn add @syntho-ui/beat-grid-geometryFree-running at 120 BPM — a plausible illustration, not the host's position. Tick Host transport and the phase starts coming from a stand-in DAW's ppqPosition; untick Transport playing and it falls back to free-running, because a face frozen on the host's last position looks broken — but note it keeps the host's tempo, since the tempo is real even when the position is not. The source field exists so a caption can state which of those is true instead of implying a sync it does not have.
useBeatClock
beat-clockWhere the playhead is — and whose clock said so.
shadcn add @syntho-ui/beat-clockControl Label
control-labelThe caption and readout wrapper the other controls share.
shadcn add @syntho-ui/control-labelTheming
Palette and finish are independent
Palette values cascade through CSS custom properties. Shape and material come from one panel-wide skin provider. Keeping those axes separate lets the same flat finish work with Bone, Midnight, Ember, or any palette you define.
Use the live Palette and Finish controls above to compare the combinations without navigating away or resetting the demo.
<ControlSkinProvider skin="flat">
<div
style={{
"--uikit-accent": "#f97316",
"--uikit-surface": "#1b1512",
"--uikit-cap": "#33241c",
"--uikit-radius": "3px",
}}
>
<Knob label="Cutoff" min={20} max={20000} defaultValue={2200} />
<Fader label="Out" min={-60} max={6} defaultValue={0} />
</div>
</ControlSkinProvider>The panel is half of it
These controls send values somewhere. The other half is the DSP behind them — written once in TypeScript, compiled to WebAssembly for the browser and to a native library for the plugin.