npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-reel-roulette

v0.1.2

Published

Horizontal reel roulette for React + Tailwind CSS. Headless hook + customizable component, dark/light ready.

Readme

react-reel-roulette

Horizontal reel roulette for React + Tailwind CSS. Realistic deceleration, headless hook, dark/light ready, zero runtime dependencies.

Live demo · GitHub · npm

Install

npm i react-reel-roulette
# or
pnpm add react-reel-roulette

Peer dependencies: React 18+. Styles are precompiled and bundled — no Tailwind setup required in your app.

Styles

The package auto-imports its own precompiled CSS (dist/styles.css), so the component works out of the box with any bundler that handles CSS imports (Vite, Next.js, webpack, etc.).

If your setup strips CSS imports from node_modules, import it manually:

import 'react-reel-roulette/styles.css'

If you use Tailwind 4 and prefer generating the utilities yourself (e.g. to dedupe with your own CSS), you can instead add the package to your scan path:

@import "tailwindcss";
@source "../node_modules/react-reel-roulette";

Quick start

import { useRef } from 'react'
import {
  ReelRoulette,
  type ReelRouletteHandle,
  type RouletteItem,
} from 'react-reel-roulette'

const prizes: RouletteItem[] = [
  { id: 'common', name: 'Dune Fragment', subtitle: 'Common', color: '#b0c3d9', weight: 80 },
  { id: 'rare', name: 'Nova Prism', subtitle: 'Rare', color: '#eb4b4b', weight: 1 },
]

export function PrizeWheel() {
  const roulette = useRef<ReelRouletteHandle>(null)

  const spin = async () => {
    const prize = await roulette.current?.spin()
    console.log('won:', prize)
  }

  return (
    <>
      <ReelRoulette items={prizes} handleRef={roulette} />
      <button type="button" onClick={spin}>
        Spin
      </button>
    </>
  )
}

Winner from your backend

// shorthand — pass the id directly
await roulette.current?.spin('rare')

// or explicit
await roulette.current?.spin({ winner: 'rare' })

Security: client-side selection by weight is cosmetic only — anyone can read it in the browser. If the prize has real value, decide it on your server and pass the id to spin(). The animation is identical.

In RouletteItem, only id is required. name, subtitle, image, color, and weight accept null, so you can pass your API response as-is.

How it works

  1. Horizontal tape inside an overflow-hidden viewport with a fixed center pointer.
  2. Each spin builds a long reel of random cells with the winner planted near the end.
  3. requestAnimationFrame + translate3d with ease-out easing: fast start, slow stop.
  4. Lands with random jitter inside the winning cell, then settles (~450 ms) to exact center.
  5. Every spin starts from the head of a fresh reel, so all spins look the same.
  6. Centering uses real DOM measurements (offsetLeft); ResizeObserver re-centers on resize.

API (summary)

| Prop | Default | Description | |---|---|---| | items | — | RouletteItem[] prize list | | handleRef | — | Imperative ref: spin()Promise<RouletteItem \| null>, reset() | | onSpinStart / onSpinEnd | — | Callbacks with the prize | | duration | 7000 | Spin duration (ms) | | reelLength | 60 | Cells generated per spin | | winnerIndex | reelLength - 8 | Cell index where the winner is planted | | landingJitter | 0.4 | Random landing inside the cell (0 = dead center) | | easing | easeOutSpin | Deceleration curve | | itemWidth / itemWidthDesktop | 112 / 144 | Card width (mobile / ≥sm) | | height / heightDesktop | 128 / 166 | Tape height | | accentColor | adaptive | Pointer color; omitted = follows theme | | card | {} | Built-in card: variant, rarityStyle, showImage/showName/showSubtitle | | pointerVariant | 'frame' | 'frame' | 'arrow' | 'line' | 'none' | | pointer | — | Custom pointer node (overrides pointerVariant) | | renderItem | default card | (cell, status) => ReactNode | | classNames | {} | root, viewport, cell | | hideEdgeFade | false | Hide lateral fade gradients | | respectReducedMotion | true | Skip animation when prefers-reduced-motion |

Headless

import { useReelRoulette } from 'react-reel-roulette'

const { reel, status, winner, spin, trackRef, viewportRef } = useReelRoulette(prizes, {
  duration: 5000,
})

Attach viewportRef to the overflow-hidden container and trackRef to the cell strip. The hook measures real DOM positions, so any responsive layout works.

Exported utilities

useReelRoulette, ReelRoulette, RouletteItemCard, easeOutSpin, easeOutQuint, easeOutCubic, easeOutExpo, easeOutBackSoft, and all TypeScript types.

Develop this repo

git clone https://github.com/FabianBarua/react-reel-roulette.git
cd react-reel-roulette
pnpm install
pnpm dev          # docs / playground at localhost:5173
pnpm build:lib    # output → dist/
pnpm build        # docs site → docs-dist/
pnpm lint

GitHub Pages (live demo)

  1. Push this repo to main.
  2. On GitHub: Settings → Pages → Build and deployment → Source → choose GitHub Actions (not “Deploy from branch”).
  3. The workflow .github/workflows/deploy-pages.yml builds with GITHUB_PAGES=true and deploys docs-dist/.
  4. After the first successful run, the site is at https://fabianbarua.github.io/react-reel-roulette/

Leave Enforce HTTPS enabled.

Publish to npm

One-time setup:

  1. Create an account at npmjs.com if you don't have one.

  2. Log in locally:

    npm login
  3. Confirm the package name is free (or bump version in package.json for updates):

    npm view react-reel-roulette version

Publish:

pnpm build:lib    # builds dist/ + .d.ts (also runs automatically via prepublishOnly)
npm publish       # use --access public if the name is scoped, e.g. @user/pkg

Checklist before publishing:

  • [ ] version in package.json is correct (semver).
  • [ ] pnpm build:lib succeeds and dist/ contains index.js + index.d.ts.
  • [ ] You are logged in as the owner of the package name (npm whoami).
  • [ ] For a first publish, the name react-reel-roulette must not be taken by another user.

After publish, consumers install with npm i react-reel-roulette.

To release a fix:

# bump version in package.json (e.g. 0.1.0 → 0.1.1)
pnpm build:lib
npm publish

License

MIT © Fabian Barua