Inputs

Dial Knob

A rotary knob input: an SVG tick ring, a subtle nudge at detents, a spring-settled pointer and a rolling readout, with wheel and arrow-key support.

Adjust a value the way you would on a hardware knob. Press and drag around the knob (or switch to up/down dragging) and the SVG tick ring and value arc track the position live; the pointer follows the target angle on a spring, so fast turns lag slightly and settle into place. With detents set, dragging near a detent clicks into it with a small nudge; the readout below rolls digit by digit and can carry a unit. The mouse wheel and full keyboard control are supported, in both controlled and uncontrolled modes.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add dial-knob.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

1
320
Active edited usageDefault props
<DialKnob/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/dial-knob.en.json

Or, once registries are configured in components.json, install it as @webberui/dial-knob.

Usage

import { DialKnob } from "@/components/ui/dial-knob";

const [temp, setTemp] = React.useState(26);

<DialKnob
  label="Temperature"
  value={temp}
  onValueChange={setTemp}
  onValueCommit={(v) => console.log(`Send ${v}°C`)}
  min={16}
  max={30}
  step={0.5}
  detents={[24]}
  unit="°C"
  formatValue={(v) => v.toFixed(1)}
/>

Props

PropTypeDefaultDescription
labelstring—Knob name, used as aria-label and shown under the readout (required)
valuenumber—Controlled value; managed internally when omitted
defaultValuenumberminInitial value in uncontrolled mode
onValueChange(value: number) => void—Called when the value changes; fires continuously while dragging
onValueCommit(value: number) => void—Called on drag release, after a key press, or ~200ms after the wheel stops
minnumber0Minimum value
maxnumber100Maximum value
stepnumber1Increment per step; values are quantized to it
detentsnumber[]—Detents, e.g. [0, 50, 100]
snapThresholdnumber3% of rangeHow close a drag must get to a detent before it snaps
arcnumber270Total rotation range in degrees, symmetric around 12 o'clock
dragMode"angle" | "vertical""angle"Turn around the center, or drag up and down (200px covers the full range)
ticksnumber27Number of ticks on the outer ring; every 5th tick is long
size"sm" | "md" | "lg" | number"md"64 / 96 / 140 px, or a pixel number
unitstring—Readout suffix, e.g. "%", "°C"
formatValue(value: number) => string—Custom readout format, also used for aria-valuetext; defaults to the decimal places of step
stiffnessnumber320Pointer spring stiffness (damping fixed at 22); higher settles faster
disabledbooleanfalseDisable all interaction
classNamestring—Passed to the outermost container

How it works

  • Angle mapping: the value maps linearly from -arc/2 to +arc/2 (0° is straight up); the tick ring, value arc and pointer all share that mapping.
  • Angle dragging accumulates the change in pointer angle around the center, wrapping each delta into ±180°, so passing 6 o'clock never jumps the value; turning back after overshooting the range responds immediately.
  • Snapping only applies to dragging: an unsnapped continuous value is kept internally, so moving past the threshold releases the detent. Keys and the wheel move one step at a time so they never get pulled back into a detent; whenever the value lands on a detent, the knob body does a 120ms wiggle, and phones that support it vibrate for 8ms.
  • The wheel listener is bound natively with passive: false and only blocks page scrolling while the pointer is over the knob; small trackpad deltas accumulate into one notch.
  • The accent comes from the --wb-dial-accent variable (neutral-900 in light, neutral-100 in dark), shared by the value arc and pointer line; transitions use --wb-duration-fast and --wb-ease-out.

Accessibility

  • The knob itself is role="slider" with tabIndex=0, aria-valuemin, aria-valuemax and aria-valuenow; aria-valuetext is the formatted value plus unit (e.g. "24.0°C") and aria-label comes from label
  • Keyboard: ← / ↓ step down, → / ↑ step up, PageUp / PageDown move 10 steps, Home / End jump to min / max
  • A focus-visible ring marks focus; when disabled, aria-disabled is set and the knob leaves the tab order
  • The visible readout and name are hidden from assistive tech to avoid double announcements
  • When the user's system has reduce motion turned on, the pointer jumps straight into place (no spring), there is no detent nudge, and the readout does not roll