Click Wheel

API Reference

Anatomy

anatomy
import { ClickWheel } from "click-wheel";

<ClickWheel.Root
  value={seconds}
  onValueChange={setSeconds}
  max={duration}
  unitsPerTurn={60}
  detent={5}
>
  <ClickWheel.Ring aria-label="Playback position">
    <ClickWheel.Rotor />
  </ClickWheel.Ring>
  <ClickWheel.Center aria-label="Play" onClick={togglePlay}>
    <PlayIcon />
  </ClickWheel.Center>
</ClickWheel.Root>

Root

Renders a div. Holds the value, the gestures, and the CSS variables.

Root props
PropTypeDefaultDescription
valuenumber—Controlled value. The accepted prop determines the value and settled progress.
defaultValuenumberminStarting value when uncontrolled.
minnumber0Lower bound.
maxnumber100Upper bound.
stepnumber1Granularity of emitted values. Also the arrow-key step. Both bounds remain reachable even when off the step grid.
unitsPerTurnnumber100The gearing: how far one full revolution moves the value.
detentnumber0Units between detents. onTick fires for every crossing, including multiple crossings in one update. 0 disables detents.
hapticsbooleantruePulse the motor when detents are crossed, where supported. Multiple crossings in one update share a pulse.
inertiabooleanfalseContinue spinning after release. Touch resumes dragging; scroll and keyboard input stop coasting.
decelerationRatenumber0.998Velocity retained per millisecond while coasting. Lower values stop sooner.
disabledbooleanfalseInert. Cancels an active interaction without committing, sets data-disabled on every part, and omits the hidden input from form submission.
namestring—Renders a hidden input with this name, for forms.
onValueChange(value: number, details: ChangeDetails) => void—Fires on every change while dragging, coasting, scrolling or keying.
onValueCommitted(value: number, details: ChangeDetails) => void—Fires once when an interaction completes, even if the value did not change. With inertia, waits until the wheel settles. Cancelled interactions do not commit.
onInteractionChange(active: boolean, details: InteractionDetails) => void—Brackets pointer, scroll and keyboard interactions, including coasting. Always ends, including cancellation and unmount.
onDraggingChange(dragging: boolean) => void—Reports only when a pointer takes or releases the ring. Use onInteractionChange to track the full interaction, including coasting, scroll and keyboard input.
onTick(direction: 1 | -1) => void—Fires per detent crossing, with the direction of travel.

Interaction lifecycle

ClickWheel.ChangeDetails contains source: pointer, wheel, or keyboard. ClickWheel.InteractionDetails also contains cancelled. An interaction starts with onInteractionChange(true), emits value changes, commits once, then ends with onInteractionChange(false). A pointer interaction includes its coast; grabbing that coast continues the same interaction. Scroll events are grouped until 150 ms of inactivity; each keyboard action commits immediately.

Disabling, pointer cancellation, losing capture, window blur during a drag, or unmounting ends the interaction with cancelled: true and no commit. Already accepted values remain. Use onInteractionChange to suspend and resume playback-driven updates, and onValueCommitted to seek. onDraggingChange only reports whether a pointer holds the ring.

Ring

Renders a div with role="slider". Pointer, scroll and keyboard input live here.

Ring props
PropTypeDefaultDescription
aria-labelstring—The accessible name. The ring is the slider.
getAriaValueText(value: number) => string—Formats the value for screen readers, e.g. seconds to “1 min 20 sec”.

Rotor

Renders an aria-hidden div with rotate: var(--click-wheel-rotation). Rotates with pointer movement. Has no additional props.

Center

Renders a button with type=button, including when composed with render. An explicit type=submit is respected. Pass onClick and an aria-label, or render a div for a plain hub. A disabled Root prevents Center activation even when a custom element overrides its disabled prop.

Common props

Every part accepts these, plus the props of the element it renders.

Common props
PropTypeDefaultDescription
classNamestring | (state) => string—A class string, or a function of the part's state.
styleCSSProperties | (state) => CSSProperties—A style object, or a function of the part's state.
renderReactElement | (props, state) => ReactElement—Replace the default element or compose with your own component. Props merge: handlers chain, classes join.

State

The object passed to className, style and render functions, exported as ClickWheel.State.

State fields
FieldTypeDescription
valuenumberThe current value.
draggingbooleanA pointer is holding the ring.
coastingbooleanThe ring is coasting after release.
disabledbooleanThe wheel is disabled.

Helpers

Haptics helpers
ExportTypeDescription
haptic(ms?)(durationMs?: number) => voidOne pulse. The duration applies to the Vibration API, default 4.
hapticsSupported()() => booleanWhether the browser supports a haptics method used by the component.
<HapticTap />input propsAn invisible switch that covers its parent so a real tap ticks on iOS. The click still bubbles.

Keyboard

Keyboard
KeyAction
Arrow Up / Arrow Right+ step
Arrow Down / Arrow Left− step
Page Up / Page Down± one sixth of a turn
Home / Endmin / max

The ring announces itself as a slider with aria-valuenow, aria-valuemin and aria-valuemax. Use getAriaValueText for a spoken format.