react-compare-slider

A lightweight, accessible React component for comparing two images, videos, or any elements with a draggable slider.

Library
npm
v4.0.0
380stars
MIT License

Repository Health

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

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
75/100Good
Architecture78
Code Quality72
Innovation62
Learning Curve88

react-compare-slider is a zero-dependency React component for visually comparing two images, videos, or arbitrary React components side-by-side with a draggable divider. It supports both landscape and portrait orientations, ships keyboard and screen-reader support out of the box, and exposes an unopinionated low-level API (react-compare-slider/components and react-compare-slider/hooks) for assembling a fully custom slider when the pre-built ReactCompareSlider component isn’t flexible enough.

The library is intentionally small and tree-shakeable. It drives the divider position through a CSS custom property registered via the CSS @property API, letting the browser interpolate transitions natively instead of re-rendering React on every pointer move. Because itemOne/itemTwo accept any ReactNode, it works for before/after photo comparisons, video comparisons, or comparing entirely custom UI, not just static images.

What You Get

  • A pre-built ReactCompareSlider component with sensible defaults for the common before/after comparison case
  • A headless useReactCompareSlider hook plus composable Root, Item, HandleRoot, Handle, and Provider primitives for full custom control
  • Built-in landscape and portrait orientation support with boundsPadding, clip, and configurable keyboard increments
  • Full keyboard navigation (arrow keys move the divider) and ARIA slider semantics for screen reader users
  • Zero runtime dependencies, tree-shakeable ESM/CJS output with TypeScript types included

Common Use Cases

  • Before/after photo comparison sliders for photo editing, design, or restoration showcases
  • Comparing two video streams or renders side-by-side (e.g. compression quality, color grading)
  • Portfolio and marketing pages showing a redesign or upgrade next to the original
  • Building a fully custom comparison widget on top of the headless hook and primitive components

Under The Hood

Architecture The repo is a pnpm workspace monorepo where lib/ holds the publishable package, alongside a docs/storybook demo/test site and a docs/example standalone app. Inside lib/src, react-compare-slider.tsx composes a Provider (React context), Root (the sized container that binds pointer/touch listeners), Item (clipped content wrapper for each side), and HandleRoot/Handle (the draggable divider), while hooks.ts’s useReactCompareSlider owns all state and event handlers (position ref, isDragging, canTransition, pointer/keyboard callbacks) and is threaded through Provider via useReactCompareSliderContext. This separation of headless state (the hook) from presentational primitives (components/) is deliberate: consumers can import react-compare-slider/components and react-compare-slider/hooks directly to assemble a fully custom slider, bypassing the pre-built ReactCompareSlider entirely. Every primitive depends on the shape of the hook’s returned context, so a breaking change to useReactCompareSlider would ripple through all of them.

Tech Stack Written in TypeScript against a React >=16.8 peer dependency, built with tsdown (an esbuild-based bundler) into dual ESM/CJS output plus .d.ts declarations, exposed through a granular exports map (., ./consts, ./components, ./hooks, ./types). The monorepo uses Biome for linting/formatting, lefthook for git hooks, release-it for publishing, and GitHub Actions for CI (separate ci.yml, review.yml, and publish-preview.yml workflows). Storybook powers both the interactive docs site and the test runner.

Code Quality Tests live under docs/storybook/content/stories/99-tests/ as Storybook play-function interaction tests (*.test.stories.tsx) alongside at least one plain Node test, executed via the Storybook test runner rather than co-located unit tests next to source. Core hook and component files are fully typed with exported prop types, consistent useCallback/useRef usage, and a dedicated check script (tsc --noEmit) run in CI alongside Biome linting. Defensive guards are minimal but present where needed, such as the zero-bounds early return in hooks.ts when the container hasn’t been measured yet.

API Design The public surface is layered for progressive disclosure: most consumers need only <ReactCompareSlider itemOne={} itemTwo={} />, while advanced users needing custom markup can import the react-compare-slider/components primitives and react-compare-slider/hooks directly. Prop names are consistent and self-documenting (boundsPadding, keyboardIncrement, onlyHandleDraggable), every prop carries a JSDoc comment with its default value, and theming constants (ReactCompareSliderCssVars, ReactCompareSliderClip) are exported as typed const objects rather than requiring consumers to guess string literals. Getting started requires no configuration beyond the two required itemOne/itemTwo props.

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