Image compare React component for Tailwind CSS v4
v1.0.0Added in 1.10.0
A before and after slider over two images or any two layers, for photo edits, AI image tools, redesigns and renovations.
Drag the handle, or anywhere on the image, to slide the divider: the before layer is revealed up to it and the after layer shows past it, following the pointer 1:1. Click or tap a spot and the divider springs there. The Before and After chips fade out while you drag and when the divider covers their corner. On a phone a sideways drag moves the divider while a vertical swipe still scrolls the page (the other way around when vertical), pinch zoom still works, and the handle has a 44px target that stays inside the frame at either end. In hover mode the divider follows a mouse without pressing; touch still drags. With the keyboard, Tab to the handle: the arrow keys move it by step, Shift moves ten steps, PageUp / PageDown move ten steps, Home / End jump to the edges, and a screen reader hears both shares.
Image compare is a Pro React component in the React media components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add image-compare, together with Motion presets, UIProvider, useControllableState and useMediaQuery and the npm package motion.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add image-comparePro item: the CLI needs a Pro account. See plans
Usage
Import ImageCompare from @/components/ui/image-compare.
import { ImageCompare } from "@/components/ui/image-compare"
export function RetouchPreview() {
return (
<ImageCompare
before="/photos/street-raw.jpg"
after="/photos/street-graded.jpg"
beforeAlt="Street photo, unedited"
afterAlt="Street photo, color graded"
beforeLabel="Raw"
afterLabel="Graded"
onValueChange={(value) => console.log(value)}
/>
)
}Props
before*string | ReactNodeThe layer shown up to the divider: an image URL, or any node (a screenshot component, a canvas, a video).
after*string | ReactNodeThe layer shown past the divider, and the one that sizes the frame when aspectRatio is auto.
beforeAltdefault"Before"stringAlt text of the before image (and its name in the slider's value text).
afterAltdefault"After"stringAlt text of the after image.
valuenumberThe divider's position in percent from the left (or the top when vertical), when you control it.
defaultValuedefault50numberThe starting position, uncontrolled.
onValueChange(value: number) => voidCalled when a drag ends, on a tap, on each key press and when the mouse leaves in hover mode. Never per frame.
orientationdefault"horizontal""horizontal" | "vertical"horizontal splits left and right, vertical splits top and bottom.
modedefault"drag""drag" | "hover"hover makes the divider follow a mouse without pressing (fine pointers only); touch always drags.
showLabelsdefaulttruebooleanChips naming each layer in its corner.
beforeLabeldefault"Before"ReactNodeThe before chip. Empty hides it.
afterLabeldefault"After"ReactNodeThe after chip. Empty hides it.
handledefault"arrows""line" | "circle" | "arrows"The grip on the divider: a slim bar, a plain knob, or a knob with arrows. Every one is a 44px target.
radiusdefault"lg""none" | "sm" | "md" | "lg"Corner radius of the frame: 0, 12, 16 or 24px.
aspectRatiodefault"16/10"stringCSS aspect ratio of the frame. "auto" lets the after layer set the height.
colordefault"#ffffff"stringThe divider line (and the line handle), any CSS color.
stepdefault1numberKeyboard step in percent; Shift and PageUp / PageDown move ten steps. Drags and taps snap to it too.
labeldefault"Comparison position"stringThe slider's accessible name.
valueText(value: number) => stringWhat a screen reader hears for a position. Default: "Before 50%, After 50%" with your label names.
classNamesPartial<Record<"before" | "after" | "handle" | "line" | "label", string>>Classes for the before and after layers (a CSS filter on an image goes here), the knob, the divider line and both chips.
Parts
ImageCompareOrientation
type"horizontal" | "vertical".
ImageCompareMode
type"drag" | "hover".
ImageCompareHandle
type"line" | "circle" | "arrows".
ImageCompareRadius
type"none" | "sm" | "md" | "lg".
ImageCompareSlot
typeThe keys of classNames.
Examples
Vertical
Top and bottom halves, a 4/3 frame and the md radius.
Hover mode
The divider tracks the mouse; the line handle keeps the photo clear.
Custom nodes
Two React nodes instead of images: the same dashboard in light and dark.
Controlled
A slider and buttons drive the divider; it springs to each new value.
Files
- components/ui/image-compare.tsx
FAQ
Is the React Image compare component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Image compare?
Run npx wingo-ui@latest add image-compare. It also adds the Wingo UI items it builds on (Motion presets, UIProvider, useControllableState and useMediaQuery) and the npm package motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/image-compare.
How does Image compare behave on phones?
On a phone a sideways drag moves the divider while a vertical swipe still scrolls the page (the other way around when vertical), pinch zoom still works, and the handle has a 44px target that stays inside the frame at either end.
