Skip to content
WANCRAFT

A.35Buttons & Controls

Rotary Knob

A tactile synth-style dial: drag, turn, scroll or use the keyboard, with ticks, a value arc and optional detents.

NewReactMotionSVGTailwindadvanced

Overview

Rotary Knob brings hardware feel to parameters like cutoff, gain or pan. Drag vertically (the way audio software does) or turn around the centre, nudge with the scroll wheel while hovering, or use arrows, Page Up / Down, Home and End. A value arc and lit tick marks show the position at a glance; bipolar mode draws the arc from the centre. With detents the dial clicks into a fixed number of positions with a short spring. The rotation and arc are motion values, and the whole control is a labelled role="slider".

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/rotary-knob.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/rotary-knob.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/rotary-knob.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { RotaryKnob } from "@/components/ui/rotary-knob";

export function RotaryKnobExample() {
  return (
    <RotaryKnob label="Cutoff" defaultValue={64} />
  );
}

03Props

PropTypeDefaultDescription
labelstring—Required. Visible label and accessible name (aria-labelledby).
value / defaultValuenumber— / 50Controlled value, or the initial value. Double-click resets to defaultValue.
onValueChange(value: number) => void—Called on every change.
onValueCommit(value: number) => void—Called when a drag ends and after key / wheel changes.
min / max / stepnumber0 / 100 / 1Range and step.
detentsnumber0Number of snap positions (≥ 2) across the range; overrides step and snaps with a spring. 0 = continuous.
sizenumber96Dial diameter in px.
ticksnumber11Tick marks around the dial; 0 hides them.
dragMode"vertical" | "circular""vertical"Drag up / right to increase, or turn around the centre.
sensitivitynumber200Pixels of vertical drag that cover the full range.
bipolarbooleanfalseDraw the arc from the centre of the range.
showValuebooleantrueShow the formatted value under the label.
formatValue(value: number) => string—Formats the readout and aria-valuetext.
wheelbooleantrueScroll wheel changes the value while the pointer is over the dial (the page does not scroll then).
accentstring"#FF7448"Arc, lit ticks, notch and focus ring colour.
disabled / nameboolean / string—Disable input; submit the value with a form via a hidden input.
...propsHTMLAttributes<HTMLDivElement>—Forwarded to the root element.

04Features

  • 01Vertical or circular dragging with pointer capture; circular mode never jumps across 6 o'clock
  • 02Scroll wheel and trackpad support with accumulated deltas
  • 03Value arc (unipolar or bipolar) plus lit tick marks
  • 04Detents that snap with a short spring
  • 05Double-click resets to the default value
  • 06Rotation and arc are motion values; nothing re-renders between steps
motion · springinput · draginput · pointerinput · keyboardinput · scrollinput · focus

05Accessibility

  • The dial is a focusable role="slider" with aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext and a visible label via aria-labelledby.
  • Arrow keys step, Page Up / Page Down move a tenth of the range, Home / End jump to the ends.
  • The wheel only acts while the pointer is over the dial, and only then prevents page scrolling.
  • Visible focus ring in the accent colour; the readout is aria-hidden because aria-valuetext carries it.
  • With prefers-reduced-motion the dial jumps to new values without springs.

06Limitations

  • Use detent counts that divide the range evenly (e.g. 11 for 0–100) to get round values.