Image Comparison

Draggable before and after image slider with hover control and spring physics.

Original ImageProcessed Image

Image Comparison is a before and after slider built from three composable pieces: an ImageSlider container, ImageLayer panels for each side, and a Divider with a draggable handle. You can drag the handle, switch to hover control, or run it vertically, and the divider movement is driven by a Framer Motion spring so it feels weighted rather than linear. Built with React and Tailwind CSS. Ideal for photo retouching showcases, redesign comparisons, and product demos where showing the difference between two states is the whole point.

Installation

Usage

import {
  ImageSlider,
  ImageLayer,
  Divider,
} from "@/components/ui/image-comparison";
<ImageSlider className="h-96 w-full overflow-hidden rounded-xl">
  <ImageLayer
    src="/path/to/before-image.jpg"
    alt="Before Image"
    layer="first"
  />
  <ImageLayer src="/path/to/after-image.jpg" alt="After Image" layer="second" />
  <Divider />
</ImageSlider>
/* Hover-controlled */
<ImageSlider
  className="h-96 w-full overflow-hidden rounded-xl"
  hoverControl={true}
  defaultPosition={20}
>
  <ImageLayer src="/path/to/before.jpg" alt="Before" layer="first" />
  <ImageLayer src="/path/to/after.jpg" alt="After" layer="second" />
  <Divider width={4} handleSize={32} />
</ImageSlider>

Props

ImageSlider

PropTypeDefaultDescription
childrenReact.ReactNodeundefinedContent of the slider, typically ImageLayer components and a Divider
classNamestringundefinedAdditional CSS classes to apply to the container
hoverControlbooleanfalseWhether to control the slider by hovering instead of dragging
orientation"horizontal" | "vertical""horizontal"The orientation of the slider
defaultPositionnumber50The initial position of the divider as a percentage (0-100)
animationConfigPartial<AnimationOptions>{ damping: 15, stiffness: 400, mass: 0.4 }Framer Motion spring animation options for the slider movement
dividerColorstring"#ffffff"Color of the divider line
constrainToContentbooleanfalseWhether to constrain the slider drag to the content area

ImageLayer

PropTypeDefaultDescription
classNamestringundefinedAdditional CSS classes to apply to the image
altstringRequiredAlt text for the image (required for accessibility)
srcstringRequiredSource URL of the image
layer"first" | "second"RequiredWhich side of the divider this image appears on
loading"lazy" | "eager""eager"Image loading behavior
prioritybooleanfalseWhether the image should be considered high priority for loading

Divider

PropTypeDefaultDescription
classNamestringundefinedAdditional CSS classes to apply to the divider
childrenReact.ReactNodeundefinedCustom content to display in the handle
widthnumber2Width of the divider line in pixels
showHandlebooleantrueWhether to show the handle on the divider
handleSizenumber24Size of the handle in pixels
handleColorstringundefinedColor of the handle (defaults to divider color if not specified)
handleIconReact.ReactNodeundefinedCustom icon to display in the handle
hitAreaSizenumber20Size of the invisible hit area around the divider