ImageKnob
A continuous control that rotates an image based on a normalized value, perfect for custom knob designs using bitmap graphics.
ImageKnob is a continuous control that rotates an image based on a normalized value. It shares the same layout, parameter model, interaction, and accessibility as other controls (vector, raster, and control primitives); see the Raster introduction.
The image rotation is determined by the normalized value (0-1 maps to rotation based on openness and rotation offset).
Note:
No Theming Support: This component does NOT support themable props (color, roundness, thickness) as visuals are entirely determined by the image content.
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
value | number | — | Required | Current value of the control |
onChange | (event: AudioControlEvent<number>) => void | — | Optional | Handler for value changes |
min | number | — | Optional | Minimum value (ad-hoc mode) |
max | number | — | Optional | Maximum value (ad-hoc mode) |
step | number | — | Optional | Step size for value adjustments |
bipolar | boolean | false | Optional | Whether the component should operate in bipolar mode (centered at zero) |
label | string | — | Optional | Label displayed below the component |
frameWidth | number | — | Required | Width of the viewBox (determines viewBox width) |
frameHeight | number | — | Required | Height of the viewBox (determines viewBox height) |
imageHref | string | — | Required | URL to the image to rotate |
rotation | number | 0 | Optional | Optional rotation angle offset in degrees |
openness | number | 90 | Optional | Openness of the arc in degrees (value between 0-360º; 0º closed, 90º 3/4 open, 180º half-circle) |
valueAsLabel | "labelOnly" | "valueOnly" | "interactive" | labelOnly | Optional | Controls how the label and value are displayed |
adaptiveSize | boolean | false | Optional | Whether the component stretches to fill its container |
size | "xsmall" | "small" | "normal" | "large" | "xlarge" | normal | Optional | Size of the component |
parameter | ContinuousParameter | — | Optional | Audio Parameter definition (Model). If provided, overrides min/max/step/label/bipolar from ad-hoc props |
valueFormatter | (value: number, parameterDef: AudioParameter) => string | undefined | — | Optional | Custom renderer for the value display |
interactionMode | "drag" | "wheel" | "both" | both | Optional | Interaction mode |
interactionDirection | "vertical" | "horizontal" | "circular" | "both" | — | Optional | Direction of interaction |
interactionSensitivity | number | — | Optional | Sensitivity multiplier for interactions (default varies by control type) |
Basic Usage
ImageKnob requires an image URL and frame dimensions:
import { useState } from "react";
import { ImageKnob } from "@cutoff/audio-ui-react";
export default function BasicImageKnobExample() {
const [value, setValue] = useState(50);
return (
<ImageKnob
value={value}
min={0}
max={100}
label="Volume"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
onChange={(e) => setValue(e.value)}
/>
);
}Rotation Configuration
Openness
Control the rotation range using the openness prop:
import { useState } from "react";
import { ImageKnob } from "@cutoff/audio-ui-react";
export default function OpennessExample() {
const [v90, setV90] = useState(50);
const [v180, setV180] = useState(50);
const [v270, setV270] = useState(50);
return (
<div className="flex gap-4 items-center flex-wrap">
<ImageKnob
value={v90}
min={0}
max={100}
label="90° Open"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
openness={90}
onChange={(e) => setV90(e.value)}
/>
<ImageKnob
value={v180}
min={0}
max={100}
label="180° Open"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
openness={180}
onChange={(e) => setV180(e.value)}
/>
<ImageKnob
value={v270}
min={0}
max={100}
label="270° Open"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
openness={270}
onChange={(e) => setV270(e.value)}
/>
</div>
);
}Rotation Offset
Apply a rotation offset to align the image:
import { useState } from "react";
import { ImageKnob } from "@cutoff/audio-ui-react";
export default function RotationOffsetExample() {
const [zeroVal, setZeroVal] = useState(50);
const [rot45Val, setRot45Val] = useState(50);
return (
<div className="flex gap-4 items-center flex-wrap">
<ImageKnob
value={zeroVal}
min={0}
max={100}
label="No Offset"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
rotation={0}
onChange={(e) => setZeroVal(e.value)}
/>
<ImageKnob
value={rot45Val}
min={0}
max={100}
label="45° Offset"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
rotation={45}
onChange={(e) => setRot45Val(e.value)}
/>
</div>
);
}Parameter Model
Use a parameter model for integration with audio parameter systems. The following example uses ad-hoc props; with the full library you would use createContinuousParameter and the parameter prop:
import { useState } from "react";
import { ImageKnob } from "@cutoff/audio-ui-react";
export default function ImageKnobParameterModelExample() {
const [value, setValue] = useState(50);
return (
<ImageKnob
min={0}
max={100}
label="Volume"
value={value}
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
onChange={(e) => setValue(e.value)}
/>
);
}Adaptive Sizing
The ImageKnob component supports both fixed sizes and adaptive sizing. By default, the size of the component is driven by the Size System and their size attribute:
import { useState } from "react";
import { ImageKnob } from "@cutoff/audio-ui-react";
export default function ImageKnobSizingExample() {
const [v1, setV1] = useState(50);
const [v2, setV2] = useState(50);
const [v3, setV3] = useState(50);
const [v4, setV4] = useState(50);
return (
<div className="flex gap-4 items-center flex-wrap">
<ImageKnob
value={v1}
min={0}
max={100}
label="Small"
size="small"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
onChange={(e) => setV1(e.value)}
/>
<ImageKnob
value={v2}
min={0}
max={100}
label="Normal"
size="normal"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
onChange={(e) => setV2(e.value)}
/>
<ImageKnob
value={v3}
min={0}
max={100}
label="Large"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
onChange={(e) => setV3(e.value)}
/>
<ImageKnob
value={v4}
min={0}
max={100}
label="XLarge"
size="xlarge"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
onChange={(e) => setV4(e.value)}
/>
</div>
);
}Adaptive sizing allows the component to adapt to the size of its parent container, adjusting its dimensions intelligently without causing distortion (scaleToFit display mode). The fill display mode makes the component fill the entire container, potentially distorting it.
import { useState } from "react";
import { ImageKnob } from "@cutoff/audio-ui-react";
export default function ImageKnobAdaptiveSize() {
const [wideVal, setWideVal] = useState(50);
const [tallVal, setTallVal] = useState(50);
const [distortedVal, setDistortedVal] = useState(50);
return (
<div className="flex gap-6 items-center flex-wrap">
<div className="w-48 h-16 border rounded-lg p-2">
<ImageKnob
value={wideVal}
min={0}
max={100}
onChange={(e) => setWideVal(e.value)}
adaptiveSize={true}
label="Wide"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
/>
</div>
<div className="w-24 h-48 border rounded-lg p-2">
<ImageKnob
value={tallVal}
min={0}
max={100}
onChange={(e) => setTallVal(e.value)}
adaptiveSize={true}
label="Tall"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
/>
</div>
<div className="w-32 h-20 border rounded-lg p-2">
<ImageKnob
value={distortedVal}
min={0}
max={100}
onChange={(e) => setDistortedVal(e.value)}
adaptiveSize={true}
displayMode="fill"
label="Distorted"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
/>
</div>
</div>
);
}Interaction Modes
Control how users interact with the knob:
import { useState } from "react";
import { ImageKnob } from "@cutoff/audio-ui-react";
export default function ImageKnobInteractionExample() {
const [dragVal, setDragVal] = useState(50);
const [wheelVal, setWheelVal] = useState(50);
return (
<div className="flex gap-4 items-center flex-wrap">
<ImageKnob
value={dragVal}
min={0}
max={100}
label="Drag Only"
interactionMode="drag"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
onChange={(e) => setDragVal(e.value)}
/>
<ImageKnob
value={wheelVal}
min={0}
max={100}
label="Wheel Only"
interactionMode="wheel"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
onChange={(e) => setWheelVal(e.value)}
/>
</div>
);
}Common Use Cases
Volume Knob
import { useState } from "react";
import { ImageKnob } from "@cutoff/audio-ui-react";
export default function VolumeKnobExample() {
const [value, setValue] = useState(50);
return (
<ImageKnob
value={value}
min={0}
max={100}
label="Volume"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-volume.png"
valueAsLabel="interactive"
onChange={(e) => setValue(e.value)}
/>
);
}Tone Knob
import { useState } from "react";
import { ImageKnob } from "@cutoff/audio-ui-react";
export default function ToneKnobExample() {
const [value, setValue] = useState(5);
return (
<ImageKnob
value={value}
min={1}
max={10}
label="Tone"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/knob-tone.png"
valueAsLabel="interactive"
onChange={(e) => setValue(e.value)}
/>
);
}Direction Indicator
import { useState } from "react";
import { ImageKnob } from "@cutoff/audio-ui-react";
export default function DirectionIndicatorExample() {
const [value, setValue] = useState(3);
return (
<ImageKnob
value={value}
min={0}
max={10}
label="Direction"
size="large"
frameWidth={100}
frameHeight={100}
imageHref="/images/demo/finger-pointing.png"
onChange={(e) => setValue(e.value)}
/>
);
}Image Requirements
- Format: Any image format supported by browsers (PNG, JPG, SVG, etc.)
- Dimensions: The
frameWidthandframeHeightprops determine the viewBox dimensions - Aspect Ratio: The image will be scaled to fit the viewBox while preserving aspect ratio
- Rotation: The image rotates around its center point based on the normalized value
Rotation Calculation
The rotation angle is calculated as:
- Normalized value (0-1) maps to rotation range based on
openness rotationoffset is added to align the image- For bipolar mode, the rotation is centered around the midpoint
Next Steps
- Explore ImageRotarySwitch for discrete rotary controls
- Learn about ImageSwitch for boolean switches
- Check out the playground for interactive examples