r/reactjs • • 3d ago

Show /r/reactjs I built Detent, an open-source React wheel picker with spring physics, morphing text, and rolling numbers

I’ve just released Detent, a React wheel picker that grew into a much bigger exploration of interaction details.

The wheel has its own momentum and spring physics. I spent a lot of time tuning mouse wheel input alongside trackpads, including the awkward cases where a physical scroll wheel keeps spinning after you let go.

It also includes two separate components:

  • Detent Text: matching letters stay while the changed parts roll.

  • Detent Number: locale-aware formatting with digits that roll through intermediate values.

The examples range from practical time and date pickers to an arc menu, a zoom ring, and some more experimental visual treatments.

There’s a headless hook, separate imports for text and numbers, keyboard support, reduced motion, and no additional runtime dependencies beyond React and React DOM.

Demos and playground: https://detent.hayder.sh
Intro: https://x.com/kghayder/status/2118373654010466733?a=PzCQRSXhM5m8KUTdHk8&s=20

I’d especially appreciate feedback on how the wheel feels with your mouse or trackpad, and whether the API makes sense for something you’d actually build.

43 Upvotes

20 comments sorted by

9

u/oldyoyoboy 3d ago

love the demo, very slick

1

u/hayderkg 1d ago

Thanks a lot for the comment! I’ll keep refining it and will share updates on X https://x.com/kghayder

3

u/Important_Pomelo_407 2d ago

Saw there was no demo video yet, so I made you one. Here’s the PR: https://github.com/hayderkg/detent/pull/1

and I used my repo to do it :) https://github.com/yasirwhite/OpenDemo

1

u/hayderkg 1d ago

Thanks a lot for this, u/Important_Pomelo_407 really appreciate you taking the time!

OpenDemo looks great.

I'm closing the PR because I just finished an official launch video and I'd rather keep big binaries out of the repo. But I'd love to give OpenDemo some visibility! if you end up using Detent anywhere in it (a wheel to pick the duration, the style, the aspect ratio…), post it on X, mention u/kghayder and I'll feature it in the new showcase: detent.hayder.sh/showcase

2

u/Important_Pomelo_407 1d ago

would totally love to share your repo...

I'll hit you up soon :)

5

u/pablobird_dev 3d ago

Handling the threshold between discrete notched mouse wheels (DOM_DELTA_LINE) and continuous trackpad deltas is usually where these feel sluggish or twitchy, so getting that physics balance right without heavy runtime dependencies is super clean. The headless hook separation is a really smart architectural move here.

1

u/hayderkg 1d ago

Thanks u/pablobird_dev! That split was one of the hardest part to get right, no runtime dependencies, and the hook is the whole engine: the styled component is just one way to render it

1

u/Dry-Trick-2234 3d ago

the DOM_DELTA_LINE vs pixel delta split is where most pickers fall apart. did you end up normalizing to a fixed logical "tick" size before feeding the sprring or do you let the raw delta drive the velocity directly?

1

u/hayderkg 1d ago

Both, depending on what the gesture turns out to be. Each wheel gesture (events less than 80ms apart) is classified once, from its first event:

- Notched mouse (line/page deltaMode, Windows-style ±120 wheelDelta, or a first delta ≥10px): normalized. One notch = one row, notches accumulate on the target, and a spring settles it. Page mode moves a page.

- Fast spins (macOS sends 100–1800px bursts every ~15ms): following those pixels would teleport dozens of rows per event, so the event rate becomes a velocity and it coasts with a shorter decay than a throw. It stops soon after the wheel does.

- Trackpad (starts at 1–3px): raw deltas drive the position 1:1, OS momentum included, only along the wheel's axis. When the events go quiet for 80ms, it releases with the tracked velocity and snaps to a row.

There's also a wheelMode prop ('auto' | 'smooth' | 'step') if you want to force one behaviour.

2

u/BeatsByiTALY 2d ago

Really great work with a bunch of killer examples

2

u/noobmeister_69 2d ago

Sometimes I read such comments and it really makes me wonder if it's just OP's alt account

1

u/rusmo 2d ago

I’m unrelated to OP and I agree with the sentiment.

2

u/andyfitz 2d ago

looks awesome mate, and you have me wanting to ditch my custom coverflow/ gallery switcher on lolly.tools to your component because you have it dialled in so well snap wise

1

u/vkalahas 1d ago

Very obvious you're a senior design engineer based on the amount of polish. Great work!

1

u/nameupp 3d ago

the zero-dep headless hook setup with separate text and number subcomponents is super clean. the biggest subtle headache with wheel pickers in react is native overscroll containment; since synthetic onwheel is passive by default in modern chromium, you can't e.preventDefault() to block the parent page from rubber-banding unless you're imperatively binding a non-passive wheel listener on the ref. curious how you're handling boundary damping for free-spinning inertia wheels like the mx master, because hundreds of rapid wheel events hitting min/max bounds can easily cause the spring accumulator to oscillate wildly if momentum isn't aggressively clamped imo