Side By Side Slide

An image comparison slider where a spring-animated divider follows the cursor on hover, revealing before/after images.

Installation

File Structure

side-by-side-slide.tsx

Usage

import { SideBySideSlide } from "@/components/unlumen-ui/side-by-side-slide";

<SideBySideSlide
  beforeImage="/images/before.jpg"
  afterImage="/images/after.jpg"
  className="aspect-video rounded-xl"
/>;

Vertical orientation

<SideBySideSlide
  beforeImage="/images/before.jpg"
  afterImage="/images/after.jpg"
  orientation="vertical"
  className="aspect-video rounded-xl"
/>

Custom divider

<SideBySideSlide
  beforeImage="/images/before.jpg"
  afterImage="/images/after.jpg"
  dividerColor="#3b82f6"
  dividerWidth={4}
  dividerShadow="0 0 16px rgba(59,130,246,0.6)"
  handleColor="#3b82f6"
  className="aspect-video rounded-xl"
/>

No handle

<SideBySideSlide
  beforeImage="/images/before.jpg"
  afterImage="/images/after.jpg"
  showHandle={false}
  className="aspect-video rounded-xl"
/>

Custom spring

<SideBySideSlide
  beforeImage="/images/before.jpg"
  afterImage="/images/after.jpg"
  springOptions={{ stiffness: 500, damping: 40 }}
  className="aspect-video rounded-xl"
/>

API Reference

SideBySideSlide

beforeImage
string

Source URL for the "before" image (left or top half).

afterImage
string

Source URL for the "after" image (right or bottom half).

beforeAlt?
string
"Before"

Alt text for the before image.

afterAlt?
string
"After"

Alt text for the after image.

orientation?
"horizontal" | "vertical"
"horizontal"

Direction of the divider. Horizontal splits left/right; vertical splits top/bottom.

initialPosition?
number
50

Starting position of the divider as a percentage (0–100). The divider springs back to this value on mouse leave.

dividerColor?
string
"white"

CSS color value for the divider line.

dividerWidth?
number
2

Thickness of the divider line in pixels.

dividerShadow?
string
"0 0 8px rgba(0,0,0,0.3)"

CSS box-shadow applied to the divider line.

showHandle?
boolean
true

Whether to render the circular drag handle on the divider.

handleSize?
number
40

Diameter of the circular handle in pixels.

handleColor?
string
"white"

Background color of the circular handle.

cursor?
"none" | "col-resize" | "row-resize" | "pointer"
"none"

CSS cursor style applied when hovering over the component. Use `none` to hide the cursor entirely.

springOptions?
SpringOptions
{ stiffness: 300, damping: 30 }

Motion spring config forwarded to `useSpring`. Controls how the divider accelerates and settles.

className?
string

Extra CSS classes applied to the root container.

Notes

  • The divider position is driven by useMotionValue + useSpring, bypassing React's render cycle for smooth 60 fps tracking.
  • Both images are sized object-cover — they always fill the container regardless of intrinsic dimensions. Wrap the component in an aspect-* class to control the ratio.
  • On mouse leave the divider springs back to initialPosition, giving users a clear preview of the default split.
  • clip-path: inset() is used to reveal the before image rather than resizing it, so both images remain full-size at all times.
  • Set cursor="none" (the default) for the cleanest look; switch to "col-resize" or "row-resize" to hint that the area is interactive.

Credits

Built by Léo. Inspired by the Side By Side Slide Framer component by Alexander Ghavas.

Keep in mind

Most components on this site are inspired by or recreated from existing work across the web. I'm not here to take credit; just to learn, experiment, and sometimes push things a bit further. If something looks familiar and I forgot to mention you, reach out and I'll fix that right away.