Rotary Knob Input — Drag Dial HTML CSS JS Snippet

Rotary Knob Input · Forms · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

270° knob sweep
Value maps to −135°…+135° with a bottom dead zone, matching real hardware knobs rather than a confusing full-circle rotation.
Conic-gradient arc
One conic-gradient with a --deg custom property renders the fill — updated with a single property write, no SVG or dash math.
Upright value readout
The number sits in a non-rotating layer while the tick spins, so the readout stays legible at every value.
Vertical-drag control
Press-and-drag up/down adjusts the value with 2px-per-unit damping — the same ergonomic pattern pro audio plugins use.
Window-level drag tracking
Move and release listeners on window keep the drag alive even when the cursor leaves the knob mid-adjust.
Scroll-wheel fine-tuning
onWheel steps the value ±2 per notch with preventDefault, enabling quick precise tweaks without dragging.
Accessible slider role
Each knob is role="slider" with arrow-key support and live aria-valuenow, so it is keyboard-operable and screen-reader friendly.
Multi-knob from one initialiser
A single forEach wires every .knob, reading and writing each one's data-val — adding a knob needs no new JavaScript.

About this UI Snippet

Rotary Knob Input — Conic-Gradient Arc, Vertical-Drag Control & 270° Value Sweep

Screenshot of the Rotary Knob Input snippet rendered live

Rotary knobs are the signature control of audio software, synth plugins, and hardware-style interfaces. They pack a continuous value into a compact, tactile dial that users can fling quickly or nudge precisely. This snippet implements a polished, multi-knob rotary control in plain HTML, CSS, and vanilla JavaScript: a conic-gradient arc that fills as the value rises, a rotating indicator tick, a live numeric readout, and three input methods — vertical drag, scroll wheel, and keyboard — all over the conventional 270° knob sweep.

The 270° sweep

Real knobs do not rotate a full circle; they sweep about 270 degrees with a "dead zone" gap at the bottom. This snippet maps value 0 to −135° and value 100 to +135°, leaving the bottom 90° empty. The indicator tick lives inside a .knob-dial that is rotated by -135 + value × 2.7 degrees (since 100 × 2.7 = 270). The numeric readout sits in a separate, non-rotating layer so it always stays upright and legible while the tick spins around it.

Conic-gradient arc fill

The coloured progress arc is a single conic-gradient on the knob element, started from 225deg to align with the bottom-left minimum. The accent colour fills from 0 to var(--deg) × 1deg, the track colour fills the rest up to 270°, and everything past 270° is transparent to create the dead-zone gap. setKnob updates the --deg custom property, so the arc redraws with a single property write — no SVG paths, no stroke-dashoffset math. The inner .knob-face is a radial-gradient circle layered on top to leave only the arc visible as a ring.

Vertical-drag control with damping

Dragging a knob in a circle is fiddly and error-prone, so professional audio software uses vertical drag instead: press and move up to increase, down to decrease. onDown records the start Y and the knob's current value; onMove applies the delta divided by 2 (so two pixels equal one unit) for a comfortable sensitivity. touch-action: none and preventDefault keep the page from scrolling mid-drag. The move and up listeners live on window, so the drag continues even if the cursor leaves the knob.

Scroll and keyboard input

onWheel nudges the value by ±2 per notch for quick fine-tuning, and onKey makes each knob a real role="slider": arrow keys adjust the value and aria-valuenow updates so screen readers announce it. A single forEach wires all three knobs from one set of handlers, reading and writing each knob's value on its own data-val attribute — so adding a fourth knob is just markup.

Pair these knobs with a range slider for linear controls, a multi-range slider for ranges, or a music player interface.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not have to work through the conic-gradient sweep math by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the gradient starts from 225deg, why value 100 corresponds to a 2.7 degree-per-unit multiplier, and how those two numbers together produce the conventional 270-degree sweep with a dead zone gap at the bottom rather than a full 360-degree rotation. The same assistant can help you optimize it — ask whether attaching mousemove and mouseup listeners on window permanently (rather than only during an active drag) has any measurable cost with several knobs on one page, and how you would scope them to only exist while a drag is active. It's also useful for extending the control: ask it to add a double-click-to-reset-to-default gesture, snap the value to fixed increments while dragging (like every 5 units) instead of continuous values, or add a visual tooltip showing the exact value while dragging instead of only the static center readout. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a rotary knob input control in plain HTML, CSS, and JavaScript with no library — a circular dial representing a 0-100 value, controllable by vertical drag, scroll wheel, and arrow keys.

Requirements:
- Render the knob's progress arc using a single CSS conic-gradient driven by one custom property representing the current degree of fill, starting the gradient from an offset angle (not from 0 degrees) so the filled and unfilled portions align with a 270-degree sweep that leaves a gap at the bottom of the circle, not a full 360-degree ring.
- Map the 0-100 value range onto that 270-degree sweep (so value 0 sits at one end of the dead zone and value 100 at the other), and rotate a separate indicator tick element by the corresponding angle using a CSS transform, keeping the numeric readout text in a non-rotating layer so it always stays upright.
- Implement value changes via vertical mouse/touch drag: moving the pointer up increases the value and moving down decreases it, with the drag's sensitivity divided down (not a 1:1 pixel-to-value mapping) for comfortable fine control, and the move/release listeners attached to the window (not the knob element) so the drag continues correctly even if the pointer leaves the small knob area mid-drag.
- Implement scroll wheel support that nudges the value by a fixed small step per wheel notch, with the default page scroll prevented while hovering a knob.
- Implement full keyboard support: each knob must be a focusable element with role slider and the appropriate aria-value attributes, where arrow keys increment or decrement the value and the aria-valuenow attribute updates to match.
- Support multiple independent knob instances on the same page from one shared set of event handler functions, with each knob's current value stored in and read from its own data attribute rather than a shared/global variable.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSThree knobs — Volume, Reverb, and Drive — appear on a dark panel, each showing its value and a coloured arc indicating its level.
  2. 2
    Drag a knob verticallyPress a knob and drag up to raise the value or down to lower it; the arc fills and the indicator tick rotates as the number changes.
  3. 3
    Scroll to fine-tuneHover a knob and scroll the wheel — each notch nudges the value by 2 for precise adjustment.
  4. 4
    Use the keyboardTab to a knob (it shows a focus ring) and press the arrow keys; the value steps and screen readers announce it via aria-valuenow.
  5. 5
    Watch the dead zoneNotice the arc sweeps 270° with a gap at the bottom — the conventional knob range, not a full circle.
  6. 6
    Add another knobCopy a .knob block, set its data-val and label, and it is wired automatically by the shared forEach initialiser.

Real-world uses

Common Use Cases

Audio mixers and synth UIs
The native habitat — volume, reverb, drive, and EQ knobs. Combine with a music player for a full playback interface.
Image and video editors
Brightness, contrast, and saturation dials; pair with an image filter editor for live adjustments.
Smart-home and IoT dashboards
Thermostat temperature, light dimming, and fan speed controls that map a value to a tactile dial.
Game and creative tool settings
Difficulty, sensitivity, and effect-intensity controls where a knob feels more expressive than a slider.
Configurator and pricing dials
Adjust a quantity or budget on a dial; combine with a usage calculator to drive a live cost.
Compact value inputs
Anywhere a range slider would take too much horizontal space, a knob packs the same 0–100 control into a small circle.

Got questions?

Frequently Asked Questions

Angular drag requires the cursor to follow a circular path and breaks down near the centre where small movements swing the angle wildly. Professional audio software solved this long ago with vertical drag: move up to increase, down to decrease. It is precise, predictable, and works the same on touch and mouse — which is why this snippet uses it.

The knob is normalised to 0–100. To use a different range, map the displayed number in setKnob (e.g. Math.round(v / 100 * (max - min) + min)) while keeping the internal value 0–100 for the arc math. Change the drag sensitivity by editing the / 2 divisor in onMove, and the wheel/arrow step by editing the 2 in onWheel/onKey.

Each knob stores its current value on data-val. Read them with [...document.querySelectorAll('.knob')].map(k => +k.dataset.val), or call your audio/parameter API from inside setKnob so every change pushes live (e.g. gainNode.gain.value = v / 100). The single update point makes wiring to a real engine straightforward.

Yes — each knob is a focusable role="slider" with aria-valuemin, aria-valuemax, and a live aria-valuenow that setKnob updates. Arrow keys adjust the value, and a visible focus ring shows keyboard focus. Give each knob a meaningful aria-label (Volume, Reverb) so screen-reader users know which control they are on.

In React, hold each knob's value in state, set the arc via the --deg style and the tick via a rotate transform, and attach pointer listeners in a useEffect (store drag start in a ref). In Vue, bind :style for --deg and use @mousedown/@wheel handlers. In Angular, bind [style.--deg] and (mousedown). The conic-gradient CSS and sweep math port unchanged.