- origin: bipolar anchor value — Arc from=center, Fader fill and LEDFader segments draw from origin instead of min/mid-travel - detents + detentSize: magnetic snap while dragging (normalized space, wheel/keyboard unaffected); applyDetents exported - DreamknobProvider + KnobTheme: accent/track/face/text/label/ticks/ focusRing/fonts tokens with dark and light bases; every skin resolves defaults from the theme, instance props win - editable + parseValue: double-click knobs (or click fader readouts) to type exact values; Enter/blur commits, Esc cancels, 1.2k parses; ValueInput + defaultParseValue exported; setValue added to context - ImageKnob: film-strip sprite knob (KnobMan-style strips), vertical or horizontal, with focus ring, editing, value/label - Docs: LED gallery cards for film-strip/bipolar/editing/theming, playground editable toggle, theming API section, README updates |
||
|---|---|---|
| .. | ||
| 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 — 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/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 }.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;parseValuecustomizes parsing ("1.2k"→ 1200 out of the box).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
ImageKnob, // film-strip sprite knob (KnobMan-style PNG strips)
SegmentDisplay, // standalone seven-segment numeric 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 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