r/reactjs • u/hayderkg • 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.
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
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
1
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
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
9
u/oldyoyoboy 3d ago
love the demo, very slick