better-scrollbar
better-scrollbar adapts to React/Vue for massive data, with virtual scrolling, custom scrollbars, dynamic sizing, sticky groups, bottom anchoring, and long-list rendering.
better-scrollbar started as a React custom scrollbar, then grew into a virtualized scroll-area system for data-heavy interfaces. The current repository is a pnpm/Turborepo monorepo with a framework-neutral core, a React adapter, a Vue 3 adapter, and a compatibility package that keeps the original better-scrollbar import path working.
Positioning
The useful way to describe this project is not "a prettier scrollbar". It is a customizable scroll container with production-grade virtualization built in.
Scroll area: renders a controlled viewport, custom track/thumb elements, native or controlled scroll modes, and horizontal plus vertical scroll state.
Virtualization core: maps logical item indexes to pixel offsets, keeps rendered DOM bounded, and supports dynamic row height correction after measurement.
Interaction layer: handles sticky group headers, bottom-following output, scroll anchoring, adaptive overscan, and scroll-seek placeholders during fast motion.
Adapters: exposes the same core behavior through React components/render props and Vue 3 components/composables.
Core Design
The heart of the project is the virtual height index in @better-scrollbar/core.
:: A framework-neutral block index backed by a Fenwick tree. Fixed-height lists use direct arithmetic; dynamic-height lists store measured deltas by block so offset lookup and range calculation do not require scanning every row.
:: A measurement feedback loop. React and Vue adapters start from estimatedItemHeight, render only the active range, observe mounted rows, then feed real heights back into the shared height index.
:: A logical-to-physical scroll bridge. Browser scroll height has practical limits, so the React adapter can compress massive logical ranges into a safe physical scroll window while preserving logical offsets for rendering.
flowchart LR Input["wheel / drag / scrollTo"] --> State["logical scroll state"] State --> Range["core virtual height index"] Range --> Window["rendered item window"] Window --> Measure["ResizeObserver + mutation checks"] Measure --> Range Range --> Bars["custom track + thumb"]
What Makes It Interesting
Area | Design choice | Why it matters |
|---|---|---|
Large lists |
| Millions of rows can be represented without mounting millions of nodes. |
Dynamic rows | measured-height cache with a default 50,000 item limit | Real row heights refine the model without unbounded memory growth. |
Sticky sections |
| Grouped data views can keep section context visible while scrolling. |
Streaming output |
| Chat, logs, and agent output can stay pinned to the bottom without fighting user scroll intent. |
Expensive rows |
| Fast movement can use lightweight placeholders until velocity drops. |
Custom visuals | render props / slots for view, tracks, and thumbs | Teams can keep product-specific scrollbar styling without forking the logic. |
Package Shape
@better-scrollbar/core: algorithms, shared types, virtual height index, overscan helpers, sticky helpers, and browser-scroll-height guards.@better-scrollbar/react:ScrollBar, ref API, render props, dynamic measurement, accessibility metadata, sticky overlay behavior, and custom scrollbar assembly.@better-scrollbar/vue:BScrollBar, scoped slots, composables, exposed scroll methods, and the same core index through Vue reactivity.better-scrollbar: compatibility wrapper that re-exports the React adapter for existing users.
Architecture Notes
The project deliberately keeps the hard math outside React and Vue. The adapters own framework lifecycle, refs, DOM measurement, and events; the core package owns range math, offset mapping, overscan normalization, sticky index helpers, and scroll-height safety.
That split matters because the same virtualization semantics can be reused in different UI runtimes. It also makes the performance story auditable: range calculation, measured-height updates, and adapter rendering can be tested independently.
Best-Fit Use Cases
Large tables, logs, traces, and timelines where the user must move through huge logical data sets.
Design systems that need branded scrollbars but cannot sacrifice accessibility or controlled scroll state.
Chat or agent-output surfaces where new content should follow the bottom until the user intentionally scrolls away.
Grouped lists with sticky headers and variable-height content.
Open-Source Signals
TypeScript-first packages with explicit exports and peer dependency boundaries.
A monorepo package layout that separates core algorithms from framework adapters.
Tests around virtual range behavior, package exports, React behavior, and Vue package shape.
Public docs for the React, Vue, and core APIs, plus a demo site for the product story.
Next Design Pressure
Keep homepage messaging focused on "virtualized scroll area", not a narrow scrollbar-only pitch.
Continue measuring the extreme-scale demos with fixed benchmark contracts instead of turning local numbers into broad browser-performance claims.
Keep advanced props documented, but present the first-use path around
itemCount,renderItem,estimatedItemHeight,height,overscan, and custom scrollbar rendering.