react-scrollbars-custom

A fully customizable, cross-browser React scrollbar component that preserves native scroll behavior, with RTL and iOS momentum support.

Library
npm
v4.1.1
857stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
41/100Fair
Development Activity0
Maintenance32
Community44
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
66/100Good
Architecture70
Code Quality72
Innovation65
Learning Curve55

react-scrollbars-custom replaces a browser’s native scrollbar chrome with a fully customizable React component while preserving native scroll behavior — it never emulates scrolling itself, only the visual thumb and track are custom-rendered. Written in TypeScript and driven by a shared requestAnimationFrame loop (Loop.ts) that synchronizes every mounted scrollbar with the browser’s render flow, it ships cross-browser thumb/track sizing, RTL auto-detection, iOS momentum scrolling, and page-zoom-aware layout out of the box.

Every internal DOM node — holder, wrapper, scroller, content, tracks, and thumbs — can be overridden via a renderer render-prop, and consumers can disable default styles entirely (noDefaultStyles) or fall back to the browser’s native scrollbars (native/mobileNative) without changing the component’s API. The library ships CJS, ES module, and untranspiled ES6+ builds plus full TypeScript types, and is a common drop-in when a project needs long-content scroll areas, chat panes, or custom-styled sidebars that must look identical across Chrome, Firefox, and Safari.

What You Get

  • Drop-in <Scrollbar> component that only requires a width/height to work
  • Renderer props for every internal element (holder, wrapper, track, thumb) for full markup control
  • Built-in RTL detection, iOS momentum scrolling, and page-zoom handling
  • TypeScript types plus CJS, ES module, and untranspiled ES6+ (esnext) build outputs

Common Use Cases

  • Custom-styled scrollable sidebars or navigation panels that must match a design system
  • Chat or message panes needing consistent scrollbar appearance across browsers
  • Dashboards with nested scroll containers where native scrollbar chrome breaks the layout
  • RTL-language applications needing correct scroll-direction handling without extra glue code

Under The Hood

Architecture The library centers on a single stateful class component, Scrollbar (src/Scrollbar.tsx, ~1,400 lines), that owns the holder/wrapper/scroller/content refs and derives a ScrollState object (types.ts) capturing client/scroll dimensions, blocked/possible scroll flags, and RTL/zoom state on every update tick. Rather than each instance running its own animation-frame loop, all mounted scrollbars register with a single shared singleton, RAFLoop (src/Loop.ts), which iterates a targets array and calls each instance’s update() once per frame — decoupling per-instance polling from the browser’s render loop so many simultaneous scrollbars stay cheap. Cross-component communication (drag events from ScrollbarThumb/ScrollbarTrack, internal lifecycle signals) flows through a bespoke Emittr event emitter (src/Emittr.ts) rather than React context, giving the library a Node-EventEmitter-like internal API independent of React’s render cycle. ScrollbarTrack.tsx and ScrollbarThumb.tsx are separate presentational components driven by react-draggable, composed back into Scrollbar through renderer props — a monolith-with-composable-leaves structure. Changing the core ScrollState shape or the RAFLoop contract would ripple through every consumer, since thumb/track sizing, RTL detection, and content-size translation all derive from that single state object each tick.

Tech Stack Written in TypeScript, targeting React as a peer dependency, with no runtime framework dependency beyond three small utility packages: cnbuilder (className composition), react-draggable (thumb drag interactions), and zoom-level (detecting browser page-zoom to correct scrollbar sizing). The package builds three output targets — CJS, transpiled ES modules, and untranspiled ES6+ — via Rollup with rollup-plugin-typescript2 and Babel, publishing typed declarations alongside. Tests run on Karma + Jasmine with karma-typescript compiling source directly against headless Chrome, coverage reported via lcov/html. Releases are automated with semantic-release gated by GitHub Actions CI, and linting uses ESLint with a shared config plus Prettier.

Code Quality Tests exist for every core module — the emitter, the RAF loop, both track/thumb components, the shared utilities, and an extensive spec for the main component — exercised through real DOM event simulation rather than shallow rendering, and the README advertises full test coverage. Error handling is deliberately minimal: the event emitter throws for invalid handler types, but most of the codebase favors defensive optional-prop checks over thrown exceptions. Naming is consistent and descriptive, types are fully authored in TypeScript with exported prop/state interfaces, and ESLint plus Prettier enforce style pre-commit with CI running lint and tests on every push. Recent commit activity has slowed considerably and open issues have accumulated, indicating the test suite is solid but active maintenance has cooled.

API Design The public surface is a single <Scrollbar> component that works with zero configuration beyond a sized wrapper, but every internal DOM node exposes a renderer render-prop receiving an element-ref callback — letting consumers fully replace markup without forking the library, a level of customization thorough compared to typical scrollbar libraries that only expose className overrides. Boolean props are grouped into clear axis-scoped families (general vs. X-axis vs. Y-axis variants) with a documented override-priority rule, reducing configuration surprises. Getting started requires only an import and a sized container, though advanced customization requires reading fairly dense documentation since the many boolean props interact non-obviously.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search