PluginUI

Knob

knobControls

Default dial pattern: label at rest, formatted value on hover and edit, with bounded caption fitting and ellipsis. Leave showLabelOnHover and valueReveal unset for that standard behavior. showLabelOnHover=false keeps label and value together; valueReveal="edit" deliberately disables hover reveal. The text variant defaults to label and value together. Module authors use FaceParamKnob to inherit schema units, taper and host edit lifecycle. Knob is the ready-made rotary control for plugin panels, with a complete SVG face and shared drag, wheel, keyboard, ARIA, host-parameter and MIDI/CC behavior. When you need a much more customized visual, use KnobHeadless: it keeps those interaction, accessibility, host-parameter and MIDI/CC contracts while renderFrame lets you draw the face yourself. For dense layouts, the ring variant is an arc-only control with a narrower track under the thicker active arc; labelPosition, showLabelOnHover, trackScale and arcCap let you tune the readout, visual weight and terminal shape without creating another control. An optional translucent interaction wedge shows the current portion of the range while the user turns the knob; pizza="never" removes it. Log and custom response curves cover non-linear parameters, while bipolar mode supports pan-style controls that grow from the centre. An optional host-injected KnobMan renderer can replace the stock SVG when available.

Bone / Studio
Loading interactive preview…
Planar / Flat
Loading interactive preview…
Midnight / Minimal
Loading interactive preview…
Ember / Vintage
Loading interactive preview…
Ink / Studio
Loading interactive preview…
Mint / Flat
Loading interactive preview…

Install

Copies the source into your project. Anything it depends on comes with it.

$ pnpm dlx shadcn@latest add @pluginui/core-knob

Pulls in

knob-headlessknob-corecontrol-skinknobman-sourceuse-knob-keyboarduse-first-interactioncontrol-labelcontrol-mathcontrol-formatcontrol-themeuse-control

First time? Add the registry to your components.json once:

components.json
{
  "registries": {
    "@pluginui": "https://pluginui.com/r/{name}.json"
  }
}

Usage

Connect the documented props to your application or host.

example.tsx
// Module face: recommended schema-bound dial. No caption behavior overrides.
import { FaceParamKnob } from "maruaudio/ui/shared/maru/module-face-controls"
<FaceParamKnob face={face} path="filter.cutoff" variant="flat"
  size={60} captionWidth={130} />

// Standalone controls: appearance and caption behavior are independent.
import { Knob } from "@/components/ui/knob"
import { KnobHeadless } from "maruaudio/ui/shared/knob-headless"
import {
  KnobmanVisualRendererProvider,
  type KnobmanVisualRenderer,
} from "@maruaudio/ui-kit/registry/lib/knobman-source"

// The host owns this adapter and may return null when the source is unavailable.
const renderKnobman: KnobmanVisualRenderer = (request) =>
  hostKnobmanRenderer(request) ?? null

<KnobmanVisualRendererProvider renderer={renderKnobman}>
  <Knob
    label="Cutoff"
    path="filter.cutoff"
    size="m"
    cursor="grab"
    min={20}
    max={20000}
    defaultValue={2200}
    curve="log"
    valueDisplay="hz"
    pizza="interaction"
    visual={{
      kind: "knobman",
      templateName: "Sonatom_red.knob",
      settingId: 2664,
    }}
    // Choose 1x, 2x, or 4x; the host also applies its bounded DPR policy.
    visualOptions={{ scale: 2, output: "frame", fit: "contain" }}
    onChange={(hz) => param.set("cutoff", hz)}
    onInteraction={() => audioContext.resume()}
  />
</KnobmanVisualRendererProvider>

// Arc-only treatment for a dense panel. The label swaps to the value while
// hovering or editing, so the control does not need a permanent readout.
<Knob variant="ring" label="Decay" labelPosition="top" min={0} max={4}
      defaultValue={1.8} trackScale={0.5}
      format={(seconds) => seconds.toFixed(1) + " s"} />

// The same geometry works for bipolar parameters. A marker is opt-in.
<Knob variant="ring" label="Pan" min={-1} max={1} defaultValue={0}
      bipolar indicator="dot" format={(value) => String(value)} />

// Persistent label AND value; split places label above and value below.
<Knob label="Cutoff" showLabelOnHover={false} labelPosition="split"
  min={20} max={20000} defaultValue={2200} valueDisplay="hz" />

// Explicit edit-only reveal: hover keeps the label. This is NOT the dial default.
<Knob label="Cutoff" valueReveal="edit" min={20} max={20000}
  defaultValue={2200} valueDisplay="hz" />

// Text-only defaults to both label/value; opt into hover/edit substitution.
<Knob variant="text" label="Tempo" min={40} max={240} defaultValue={120}
  unit="BPM" showLabelOnHover />

// Fixed type size with ellipsis, instead of the default caption compression.
<Knob label="A deliberately long parameter caption" captionWidth={100}
  labelCompressible={false} defaultValue={0.5} min={0} max={1} />

// Need a completely custom face? KnobHeadless keeps the same interaction,
// ARIA, host-parameter and MIDI/CC wiring while renderFrame draws the visual.
<KnobHeadless
  {...gestureProps}
  path="filter.cutoff"
  ccLabel="Cutoff"
  ccRange={{ min: 20, max: 20000, unit: "Hz" }}
  renderFrame={({ control, chrome }) => (
    <YourCustomKnob control={control} chrome={chrome} />
  )}
/>

Props

PropTypeDefaultDescription
renderDesign(state: KnobDesignState | FaderDesignState) => ReactNode—Replace drawing while retaining the same value, formatter, keyboard, reset and edit gestures. Design receives normalized value01 and formattedValue; Fader also supplies orientation and capInset.
hideLabel / hideValueboolean—Hide the stock caption/readout when your layout places them elsewhere. Accessible name and formatted value stay on the shared control.
valuenumber—Controlled value. Omit to let the control own its state.
defaultValuenumber0Starting value when uncontrolled, and the double-click reset target.
onChange(value: number) => void—Fires on every value change, in both controlled and uncontrolled modes.
onChangeEnd(value: number) => void—Fires when pointer, wheel or keyboard editing finishes. Use it to coalesce changes into one undo step.
minnumber0Lower bound of the range.
maxnumber1Upper bound of the range.
stepnumber0Quantisation grid. 0 means continuous.
labelstring—Visible caption and accessible parameter name. Placement follows the component.
unitstring—Suffix appended by the default formatter, e.g. "Hz".
format(value: number) => string—Custom readout formatter. Overrides `unit`.
hideValuebooleanfalseHide the numeric readout.
disabledbooleanfalseBlock interaction and dim the control.
pathstring—Stable host parameter path, kept separate from the visual source identity (for example, `filter.cutoff`).
cursorCSSProperties["cursor"]"ns-resize"Cursor shown over the knob. "grab" changes to "grabbing" while adjusting; use "default" or "auto" to keep the ordinary browser cursor.
variant"text" | "default" | "small" | "mini" | "large" | "minimal" | "detailed" | "flat" | "panel" | "ring" | "vintage" | "stepped" | "studio" | "neon" | "instrument" | "molten""default"Visual preset. panel uses a neutral cap and rectangular pointer, rounded 4px rings at every size, and no interaction wedge or glow. Scope --uikit-accent on a panel to color only its progress rings. ring is the capless arc-only treatment with a thinner unlit track under the active arc; flat is a matte cap, vintage and stepped engrave a ticked skirt, and neon keeps the arc glowing at rest. Prefer size for semantic layout sizing; instrument and molten provide shared themed panel skins. An explicit variant wins over an inherited skin.
skin"default" | "flat" | "vintage" | "minimal" | "neon"—Finish shared with every other control in the kit. If omitted, the knob uses the finish declared by a surrounding ControlSkinProvider. This lets one panel declare one finish for all its controls.
size"xs" | "s" | "m" | "l" | "xl" | number"m"Semantic size preset from extra-small to extra-large, or an explicit diameter in pixels. m is the current/default size.
visualKnobmanVisualSource—Optional host-resolved visual identity. Keep the gallery `templateName` exact and `settingId` numeric; it does not replace the knob's parameter `path`.
visualOptionsKnobmanVisualOptions—Rendering policy passed to the injected host renderer. `scale` is explicitly 1x, 2x, or 4x; `output` selects a frame or strip and `fit` selects contain or cover.
indicator"line" | "dot" | "none"variant's ownHow the position is marked on the control: a radial line, a classic potentiometer dot near the rim, or nothing. The ring variant defaults to no marker; opt into a line or dot when the panel needs one.
arcCap"butt" | "round" | "square""round" for panel, "square" otherwiseTerminal geometry for both arc strokes. square is flat and extends by half a stroke width, butt is flush to the path endpoint, and round restores rounded terminals.
arcWidthnumber—Ring width in pixels, independent of diameter. Sets both arc and track before an optional trackScale; constrained to 1px through diameter/6. Panel defaults to 4px; other styles retain their proportional width when omitted.
trackScalenumber—Multiplier on the unlit track stroke. Values below 1 make the track quieter while preserving the active arc width; omitted uses the variant preset.
showTrackbooleantrueDraw the unlit arc track behind the value arc. Turn it off to show only the lit arc or the wedge.
showCenterbooleanfalseDraw a centre detent tick. Bipolar knobs always show one.
pizza"interaction" | "always" | "never""interaction"Optional interaction feedback. "interaction" briefly shows a translucent wedge over the current value while you turn the knob; "always" keeps it visible, and "never" leaves it out.
activeColorstring—Colour of the wedge, arc and marker. Defaults to the --uikit-accent token.
curve"linear" | "log" | "exp" | { mapTo01, mapFrom01 }"linear"Standalone response curve for gesture and visual. Log requires positive bounds; exp includes zero; custom mappings must be inverses. In a module, use the parameter descriptor taper through FaceParamKnob rather than a face-only curve.
valueDisplay"raw" | "percent" | "db" | "hz" | "ms"—Preset readout format. Ignored when `format` is given.
displaystring—Explicit readout text, useful for instrument panels with custom units.
accentClassstring—Optional utility class for the lit arc and pointer.
glowbooleanfalseKeep the instrument-style cap glow active while the host reports live playback.
labelPosition"top" | "bottom" | "left" | "right" | "split""bottom"Caption placement. split places the label above and value below; a substituted label/value still uses one stable slot.
showLabelOnHoverbooleantrue for dials; false for textSwap the caption for the formatted value while hovering or editing. The fixed-width readout slot prevents the control from moving when the text changes; set false to keep both label and value visible.
valueReveal"hover-or-edit" | "edit""hover-or-edit"Trigger for substituted values. Standard hover-or-edit reveals on pointer hover, drag, wheel and adjustment keys. edit explicitly suppresses hover reveal; it does not mean label-only at rest.
captionWidthnumberknob diameterCaption width in pixels, independent of dial size. A bounded slot prevents layout shifts and supports ellipsis; widen the caption without enlarging the dial.
labelCompressiblebooleantrueFit long labels within readable bounds, then use single-line ellipsis. False preserves fixed typography and uses ellipsis directly. The full accessible parameter name remains available.
labelNarrowbooleanfalseCondensed caption typography without changing dial geometry or numeric formatting.
textStylesKnobTextStyles—layout, label, value and unit style slots. layout applies to text-only controls; typography slots work across variants. Preserve the shared overflow boundary and formatting policy.
unitSizeCSSProperties["fontSize"]0.4em for text; unsplit for dialsIndependent unit suffix size, for example 0.6em. Shared formatting retains precision and units.
tickCountnumbervariant-specificEngraved skirt tick count; 0 hides ticks.
scaleLabelsreadonly KnobScaleLabel[]—Printed value/label pairs around the dial, positioned using the same parameter response curve.
includeIntoTabOrderbooleantrueInclude the control in keyboard navigation. Prefer the default for usable module faces.
midiControlMidiProps—Explicit CC assignment/menu/activity wiring. A stable path also resolves mapping from the host provider; module faces use FaceParamKnob rather than building another mapping system.
titlestring—Optional author tooltip. No default tooltip is added; caption substitution and full ARIA name/value are independent of this prop.
className / stylestring / CSSProperties—Outer layout/theme styling. Caption slots use textStyles; retain the shared bounded readout instead of overriding overflow or rebuilding a label.
aria-labelstring—Accessible name when no visible label is supplied.
sweepnumber—Deprecated alias: use rotationRange in new faces.
rotationRangenumber270Total arc travel in degrees. 270 is the classic hardware throw.
rotationOffsetnumber-135Where the sweep begins, in degrees from twelve o'clock.
bipolarbooleanfalseLight the arc out from the centre and draw a detent tick, for pan-style parameters.
interaction"vertical" | "horizontal" | "xy" | "circular""vertical"Which drag axis turns the knob. A single-axis drag is judged by angle, not locked to the axis, so a diagonal drag still counts. Circular mode follows the pointer's angle around the knob. It honours rotationOffset, holds rather than flips when the pointer crosses the gap below the sweep, and ignores the few pixels around the spindle.
dragDistancenumber126 at m sizePixels of drag needed to cross the whole range. Smaller knobs scale this down; pass an explicit value to override it.
axisTolerancenumber20Degrees from the cross axis inside which a single-axis drag is ignored. The default only refuses drags running within 20° of the wrong axis, so ordinary diagonal drags still turn the knob; 0 accepts any drag with a component along the axis.
wheelSensitivitynumber1/70 ≈ 0.0143Normalised units per wheel notch. The default crosses a continuous range in about 70 notches and guarantees one declared step per notch for stepped ranges; 0 disables the wheel.
onInteraction(event) => void—Fires once, synchronously inside the first gesture. Use it to resume a suspended AudioContext.

Interaction

  • Standard dial caption Label at rest, value on hover or edit. Leave reveal flags unset; the label returns after the pointer and editing gesture leave.
  • Caption alternatives showLabelOnHover=false keeps both label and value visible. valueReveal=edit explicitly excludes hover; text defaults to the persistent two-part view.
  • Long labels Constrain captionWidth. Shared readable fitting falls back to ellipsis; labelCompressible=false chooses fixed typography. ARIA keeps the complete name/value.
  • Pointer and wheel Vertical drag by default, optional horizontal/xy/circular. Shift adjusts finely; wheel uses the declared step and wheelSensitivity.
  • Keyboard and reset Focus with Tab, adjust with arrow keys and Home/End, and double-click to reset to defaultValue. Values reveal during adjustment.
  • Module pattern FaceParamKnob derives units, bounds and taper from the schema and wires the shared host edit/CC contract. A custom visual does not own another parameter system.