- 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 |
||
|---|---|---|
| .. | ||
| src | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
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 viadecimals.values={[0.25, 0.5, 1, 2, 4]}— discrete allowed values.steps={5}— evenly spaced detents (selector knobs).taper—linearTaper(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