DreamKnob/packages/dreamknob/README.md
Dreamodus 54d006b137 Release 1.0.0: license, changelog, packaging closeout
- MIT license attributed to Dreamodus Software Inc. (root + package
  LICENSE, package.json author, README and docs-site footer)
- Version 1.0.0; CHANGELOG.md summarizing the release
- Package files whitelist includes LICENSE/README/CHANGELOG; tarballs
  gitignored
- Root README: instructions for consuming the unpublished package
  (tarball, file dependency, or pnpm link)

Verified: 32 tests, both packages typecheck, production builds clean,
and a pnpm-pack tarball installed into a fresh project resolves all 50
exports via both ESM and CJS.
2026-07-12 21:05:34 -07:00

6.3 KiB
Raw Blame History

dreamknob

Studio-grade React knobs & faders. Rotary, linear, 2D, 3D, digital and analog — with a headless core, any number range, any decimal precision, and colors you fully control. Zero runtime dependencies.

npm install dreamknob

Quick start

import { FlatKnob } from 'dreamknob'

function Volume() {
  const [value, setValue] = useState(64)
  return (
    <FlatKnob
      size={90}
      value={value}
      onChange={setValue}
      min={0}
      max={100}
      step={0.5}
      label="Volume"
      unit="%"
      aria-label="Volume"
    />
  )
}

Uncontrolled works too: pass defaultValue instead of value/onChange (it also becomes the double-click reset target).

Interaction model

Every component shares one core:

Gesture Behaviour
Drag rotary (grab & turn, follows the pointer angle), vertical, horizontal, both, or absolute track-* (faders)
Scroll wheel one step per notch (else 1% of range); Shift = fine on continuous values — snapped controls always move whole steps
↑ → / ↓ ← ± one step (Shift = 10×)
PageUp/PageDown ± 10 % of range
Home/End min / max
Double-click reset to defaultValue — or open the type-in editor with editable
Escape cancel an in-flight drag, restoring the start value
Touch / pen pointer capture, touch-action: none

All knobs render role="slider" with aria-valuemin/max/now (and aria-valuetext when you provide getAriaValueText).

Value handling

  • min / max — any numbers (negative, fractional, huge).
  • step — decimal snap increments (0.5, 0.001, 1e-7…) with float-drift-safe rounding; display precision inferred automatically, overridable via decimals.
  • values={[0.25, 0.5, 1, 2, 4]} — discrete allowed values.
  • steps={5} — evenly spaced detents (selector knobs).
  • taperlinearTaper (default), logTaper (frequencies), powTaper(n) (gain), or your own { toNormalized, fromNormalized }.
  • origin={0} — bipolar anchor: arcs/fader fills draw from this value (pan, gain trim).
  • detents={[0]} + detentSize — magnetic snap points while dragging.
  • editable — double-click (knobs) or click the readout (faders) to type an exact value; parseValue customizes parsing ("1.2k" → 1200 out of the box).
  • rotaryMode'absolute' (rc-knob feel), 'relative' (no grab jump), or 'pickup' (MIDI-style: engages when the pointer sweeps past the value).
  • dragAcceleration — velocity gain for relative drags; hideCursorOnDrag hides the cursor mid-gesture.
  • wheelStep / wheelRequiresFocus — tune wheel notches, or keep hover-scroll free.
  • onChange / onChangeStart / onChangeEnd — gesture-aware callbacks with meta.source ('drag' | 'wheel' | 'keyboard' | 'reset' | 'api').
  • wrap — endless encoder mode: values roll around min↔max (pair with angleOffset={0} angleRange={360}).
  • animateChanges — tween the pointer on programmatic changes (preset loads); gestures never animate and prefers-reduced-motion is honored.
  • valueBubble — floating readout above the control while dragging.
  • name — hidden form input for plain <form> posts on every knob, fader and PushButton (XYPad uses nameX/nameY). Interactive components forward a ref to their root element, and primitive-drawn SVG parts carry data-part attributes for CSS styling.

Prebuilt skins

import {
  FlatKnob,     // clean 2D arc knob (arcFrom="center" for pan knobs)
  MetalKnob,    // 3D brushed aluminum, knurled rim (tone="silver" | "dark")
  RubberKnob,   // 3D soft-touch synth knob with glow halo
  VintageKnob,  // chicken-head bakelite over a printed scale
  LEDKnob,      // segmented LED ring + true seven-segment readout
  NeonKnob,     // glowing arc for dark UIs
  SteppedKnob,  // detented selector (positions={['LP','BP','HP']})
  Fader,        // linear channel fader, vertical or horizontal
  LEDFader,     // segmented LED meter-fader with color zones
  ImageKnob,    // film-strip sprite knob (KnobMan-style PNG strips)
  Meter,        // read-only LED level meter with peak hold + latching clip LED
  XYPad,        // two-parameter pad (cutoff/resonance, vector mixing)
  PushButton,   // panel button with LED strip (toggle or momentary)
  SegmentDisplay, // standalone seven-segment numeric display
  AlphaDisplay,   // fourteen-segment alphanumeric display
} from 'dreamknob'

Theming

import { DreamknobProvider } from 'dreamknob'

<DreamknobProvider base="dark" theme={{ accent: '#ff4d6b' }}>
  {/* every control below inherits the tokens; instance props still win */}
</DreamknobProvider>

base="light" swaps in light-background defaults. Tokens: accent, track, face, text, label, ticks, focusRing, fontMono, fontUI.

Every knob skin and fader takes the core props plus size, label, showValue, unit, format, and per-part color props (color, trackColor, faceColor, bodyColor, …). VintageKnob has no readout, so it omits showValue/unit/format; the display components (Meter, SegmentDisplay, AlphaDisplay) have their own smaller prop sets.

Also exported for custom builds: primitives (Arc, Pointer, Ticks, TickLabels, Face, KnobValue, KnobLabel, GlowFilter), theming (useKnobTheme, darkTheme, lightTheme), editing (ValueInput, defaultParseValue), colors (mixColors, sampleGradient), and the math kit (clamp, roundTo, snapToStep, decimalsFromStep, findClosest, applyDetents, tapers, angle/arc helpers).

Compose your own

import { Knob, Arc, Pointer, Ticks, KnobValue } from 'dreamknob'

<Knob size={96} min={-24} max={24} step={0.5} defaultValue={0} aria-label="Trim">
  <Ticks count={25} activeColor="#ffd23e" />
  <Arc radius={36} thickness={3} color="#ffd23e" from="center" />
  <Pointer type="triangle" radius={33} length={9} width={8} color="#ffd23e" />
  <KnobValue unit=" dB" />
</Knob>

<Knob> also accepts a render function: {(ctx) => <YourSVG angle={ctx.angle} />}.

Fully headless

import { useKnob } from 'dreamknob'

const { value, normalized, angle, isDragging, bind, setValue } = useKnob({
  min: 0, max: 11, step: 0.1, defaultValue: 11, 'aria-label': 'Volume',
})
return <div {...bind}>your own rendering</div>

License

MIT © Dreamodus Software Inc.