react-scroll-sync
Synchronizes scroll position across multiple scrollable panes in React with a simple context-based provider and pane wrapper.
Repository Health
Technical Analysis
react-scroll-sync is a small, focused React library for keeping several scrollable containers in lockstep. A ScrollSync provider tracks the panes registered beneath it, and any ScrollSyncPane wrapped around a scrollable child automatically mirrors its scroll position to every other pane in the same group whenever one of them is scrolled.
The library supports both proportional syncing (scaling scroll offsets to each pane’s own scrollable range, useful when panes have different content heights) and absolute syncing (mirroring raw scrollTop/scrollLeft values), and can sync horizontally, vertically, or both independently. Panes can be grouped by name so that only related panes stay in sync, and an attachTo ref option lets you sync an element that isn’t a direct child, such as a table body scrolled via a custom container.
Commonly used for side-by-side diff viewers, synchronized data tables with a frozen header or sidebar, dual-pane code/preview editors, and any UI with parallel scrollable regions (translation comparison, spreadsheet-like grids, multi-column layouts) that should feel like one continuous surface.
What You Get
ScrollSyncprovider component that tracks registered panes and coordinates synchronization between themScrollSyncPanewrapper that clones its child element, attaches scroll listeners, and registers/unregisters it with the nearestScrollSynccontext- Proportional scroll sync (scales offsets to each pane’s own scrollable range) or absolute sync (mirrors raw scrollTop/scrollLeft), toggled via a
proportionalprop - Independent horizontal and vertical sync toggles (
horizontal/verticalprops) plus a globalenabledflag and a per-paneonSynccallback - Named/grouped panes via a
groupprop (string or array) so multiple independent sync groups can coexist on one page - An
attachToref option to sync scroll on an element other than the pane’s direct child, and a full TypeScript type definitions bundle shipped with the package
Common Use Cases
- Side-by-side diff or comparison viewers where two code/text panes must scroll together
- Data tables with a frozen header row or sidebar column that needs to track the scrolling body
- Dual-pane markdown/code editors with a live preview pane that mirrors the editor’s scroll position
- Spreadsheet-like grids or multi-column layouts where several scrollable regions should feel like one surface
- Translation or localization review tools that show source and target text in parallel scrolling panes
Under The Hood
Architecture
The library is a single React context (ScrollSyncContext, defined in src/hooks/useScrollSyncContext.ts) paired with two components: ScrollSync (src/components/ScrollSync.tsx), which owns a panesRef map of group name to registered DOM nodes and exposes registerPane/unregisterPane through the context, and ScrollSyncPane (src/components/ScrollSyncPane.tsx), which clones its single child to attach a ref, then registers that DOM node with the provider on mount and unregisters it on unmount. Scroll handling is attached directly as a native onscroll DOM handler rather than a React event, and addEvents/removeEvents temporarily detach a pane’s own listener while it is being programmatically scrolled (wrapped in nested requestAnimationFrame calls) so that mirroring one pane’s scroll never re-triggers a synchronization loop back through the others. This keeps the abstraction to two composable components with no external state library, and the whole implementation lives in a handful of small, single-purpose files.
Tech Stack
Written in TypeScript targeting React 16 through 19 as peer dependencies (no runtime dependencies of its own), built with tsup to emit both ESM and CJS output plus .d.ts/.d.cts type declarations, matching the package’s dual exports map. Documentation and interactive examples are built with Storybook (storybook, @storybook/react-webpack5), and husky plus lint-staged run linting on commit. There is no server, database, or build-time framework involved — it is a pure client-side DOM/React utility.
Code Quality
No dedicated unit test files were found in the repository (no *.test.*/*.spec.* files); confidence in correctness instead relies on the Storybook stories under stories/ serving as manual/visual verification, along with a CI release workflow (.github/workflows/release.yml). Code is fully typed with explicit prop interfaces (ScrollSyncProps, ScrollSyncPaneProps) and JSDoc comments on every prop, uses eslint with typescript-eslint and eslint-plugin-react/react-hooks for static checks, and follows consistent naming and hook-based patterns (useCallback, useRef, useEffect) throughout. The absence of automated tests is the main gap relative to a fully mature setup.
What Makes It Unique
Rather than a general-purpose scroll-position state manager, it solves one narrow problem precisely: bidirectional, loop-safe synchronization between an arbitrary number of grouped panes, with a proportional-scaling mode that correctly handles panes of different content sizes (not just raw offset mirroring). The attachTo escape hatch and the ability to belong to multiple named groups simultaneously give it flexibility uncommon in similarly small scroll-sync utilities, while keeping the public API down to two components and a handful of props.
Used by 2 apps in this directory
Bigcapital
Invoicing Finance
Self-hostable double-entry accounting platform with invoicing, inventory, multi-currency, and real-time financial reporting for small and medium businesses.
Notesnook
Note Taking · File Storage · Security
End-to-end encrypted, open-source note-taking where your data stays yours — even from the server.