caliper uidocs

Rotary knob

Free

A rotary control with a real edge. The knob is a short cylinder seen from above: a stack of discs gives it thickness, a ring of ticks lights up to the value, and the number is always written beside it. Drag it or use the keys; with detents on it clicks through every tenth.

Installs components/ui/rotary-knob.tsxDepends on @radix-ui/react-slot, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add @caliper/rotary-knob
42% gain

Drag, or use the arrow keys, Home and End.

When to use #

Use it for

  • Gain, volume, brightness and other controls that feel like a physical dial.
  • Settings where the number matters less than the feel of turning it.

Skip it when

  • Precise entry; pair a slider with a number field.
  • Forms that need a native control for autofill or validation.

Decisions #

Why it is built this way, so you know what to keep when you change your copy.

Behaviour

Drag uses straight lines
Right or up raises the value, left or down lowers it, so nobody has to circle the knob. It works the same on touch.
It is a slider to assistive technology
role slider with the value and a spoken value text. Arrows, PageUp, PageDown, Home and End all work, and Shift steps by a tenth.

Styling

Ember marks the one thing that moves
The notch on the cap is the only brand colour; the ticks light in the foreground colour.

Installation #

terminal
pnpm dlx shadcn@latest add @caliper/rotary-knob

Adds its 5 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above.

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

export function RotaryKnobDemo() {
  const [gain, setGain] = useState(42);
  return (
    <div className="w-full max-w-sm">
      <RotaryKnob label="Gain" unit="%" caption="% gain" value={gain} onValueChange={setGain} resetValue={50} valueText={(v) => `${v} percent gain`} />
    </div>
  );
}

API reference #

RotaryKnob

component
PropTypeDefaultDescription
label *string—Accessible name of the control, such as "Gain".
valuenumber—Controlled when set. Whole numbers between `min` and `max`.
defaultValuenumber—
onValueChange((value: number) => void)—
minnumber0
maxnumber100
detentnumber10The step the knob clicks through while snapping is on. 0 turns detents off for good.
unitstring—Unit shown beside the number, such as "%".
valueText((value: number) => string)—The spoken value. Defaults to the number and the unit.
captionReactNode—The small text beside the number. Defaults to the unit and the label. Long text wraps.
resetValuenumber—Shows a Reset button that returns the knob to this value.
controlsbooleantrueThe Snap toggle and Reset button under the knob.
hintstring"Drag, or use the arrow keys, Home and End."
statusDepthStatus"ready"
messagesDepthMessages—
onRetry(() => void)—
disabledboolean—
classNamestring—

Accessibility #

It exposes slider semantics, with aria-label, aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext and aria-describedby kept in step with state.

KeyBehaviour
↑Moves to the previous item or value.
→Moves to the next item or value.
↓Moves to the next item or value.
←Moves to the previous item or value.
PageUpMoves by a larger step, or to the previous page.
PageDownMoves by a larger step, or to the next page.
HomeJumps to the first item or the minimum.
EndJumps to the last item or the maximum.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.