DreamKnob/packages/dreamknob
Dreamodus da352d3695 Audit fixes: a11y, gesture correctness, math edge cases
- Keyboard focus-visible ring on all controls (WCAG 2.4.7), suppressed
  for pointer grabs; focusRing prop to customize or disable
- aria-orientation on track modes; data-dragging/-disabled/-readonly/
  -focus-visible state attributes; disabled dims, readOnly drops the
  grab cursor
- Escape cancels a drag and restores the pre-gesture value
- Controlled mode no longer desyncs when the parent rejects onChange
- Second concurrent pointer can no longer hijack a drag session;
  drags cancel when disabled/readOnly flips mid-gesture
- Wheel: onChangeStart symmetry, settle flush on unsubscribe, dominant-
  axis direction (fixes macOS Shift+scroll reversal), ignored mid-drag
- Shift+wheel and fine multipliers now work on stepped/values knobs
- Fader/LEDFader track mapping inset-aligned with handle travel (no
  value jump when grabbing the cap); LEDFader zones sorted
- roundTo no longer returns NaN for exponential-notation values;
  logTaper falls back to linear for invalid ranges; decimalsFromStep
  shaves float noise; findClosest([]) returns the input
- Double-click reset brackets with onChangeStart/End
- MetalKnob honors showValue/unit/format; VintageKnob omits them from
  its type; TickLabels extracted as a public primitive
- SegmentDisplay: letters/hex charset, dash overflow instead of 888
2026-07-12 14:38:50 -07:00
..
src Audit fixes: a11y, gesture correctness, math edge cases 2026-07-12 14:38:50 -07:00
package.json Initial release: dreamknob library + showcase 2026-07-12 14:26:52 -07:00
README.md Initial release: dreamknob library + showcase 2026-07-12 14:26:52 -07:00
tsconfig.json Initial release: dreamknob library + showcase 2026-07-12 14:26:52 -07:00

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, Shift = fine
↑ → / ↓ ← ± one step (Shift = 10×)
PageUp/PageDown ± 10 % of range
Home/End min / max
Double-click reset to defaultValue
Touch / pen pointer capture, touch-action: none

All knobs render role="slider" with aria-valuemin/max/now/text.

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 }.
  • onChange / onChangeStart / onChangeEnd — gesture-aware callbacks.

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
  SegmentDisplay, // standalone seven-segment numeric display
} from 'dreamknob'

Every skin takes the core props plus size, label, showValue, unit, format, and per-part color props (color, trackColor, faceColor, bodyColor, …).

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>

MIT