Skip to content

Image Compare

Compare two pictures in one frame: drag the divider to reveal one over the other.

import { ImageCompare, ImageCompareImage } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use an Image Compare to show what changed between two versions of the same picture: an edit, a redesign, a model's output against its input, a site before and after a fix. For pictures to step through one at a time, use a Carousel.

import { ImageCompare } from '@youj/react'

<ImageCompare
  before={{ src: '/room-before.jpg', alt: 'The room before the renovation' }}
  after={{ src: '/room-after.jpg', alt: 'The room after the renovation' }}
  beforeLabel="Before"
  afterLabel="After"
  className="aspect-video"
/>

before and after take { src, alt } for an image (drawn cover-fit) or any node: an illustration, a canvas, a rendered component. after sits underneath and sets the size; before is cut off at the divider. Give both the same aspect, or set one on the component with className.

The position is a number from 0 to 100: how much of before shows. It is uncontrolled with defaultValue (50), or controlled with value and onValueChange.

Reference

Propsbefore, after, value, orientation, beforeLabel, afterLabel, hover…

Props

ImageCompare props
PropType
beforeRequiredImageCompareSide

Shown on the start side (or the top), up to the divider. A node, or { src, alt } for an image.

–
afterRequiredImageCompareSide

Shown past the divider. A node, or { src, alt } for an image. It sets the component's size.

–
valuenumber

The divider's position, 0–100: how much of before shows. Controlled.

–
defaultValuenumber

The starting position when uncontrolled.

default 50

50
onValueChange(value: number) => void–
orientation'horizontal' | 'vertical'

horizontal (side by side, the default) or vertical (stacked, the divider runs across).

default 'horizontal'

'horizontal'
beforeLabelReactNode

A tag on the before side, such as "Before" or "Original". Also names that side in the divider's value text.

–
afterLabelReactNode

A tag on the after side.

–
hoverboolean

The divider follows a mouse without a press. Touch and pen still drag.

default false

false
disabledboolean

Blocks dragging, hovering and keys, and takes the handle out of the tab order.

default false

false
soundboolean

With sound on (YoujProvider’s sound), pressing the handle taps and each key step ticks. false keeps it silent.

default true

true
Other props pass through: extends ComponentProps<'div'>.

Data attributes

The root carries data-orientation (horizontal or vertical), plus data-dragging while a pointer holds it and data-disabled when disabled. Its parts are image-compare-before, image-compare-after, image-compare-divider, image-compare-handle and image-compare-label (with data-side). The drawn position is --ui--compare, from 0 to 100.

MotionDrag, press, keys · reduced motion

Motion

Motion tokens
MomentTokens
Drag and hoverthe divider follows the pointer 1:1, never eased in time
no token

Press to jumpa press away from the handle springs the divider to the pointer
spring.snappy

0.2s, bounce 0

0.2s, bounce 0
Value from outsidea new controlled value springs the divider there
spring.snappy

0.2s, bounce 0

0.2s, bounce 0
Key stepeach arrow, Page, Home or End step glides
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Handle pressthe held handle shrinks to 90% and its shadow tightens
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Corner tagsa tag fades out as the divider comes near it
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

Dragging is the same. A press, a key or a new value puts the divider there at once, and the handle and tags change without easing.

Reduced motion

Dragging works the same. Presses, keys and new values move the divider at once, and the handle and tags change without easing.

AccessibilitySlider role, keyboard, value text

Accessibility

  • The handle is a slider (0 to 100) with aria-orientation, named "Comparison divider". Its value text says how much of each side shows ("Before 60%, After 40%"), using beforeLabel and afterLabel when they are strings.
  • Arrow keys step by 5 and Page Up and Page Down by 20; Home and End go to either end. Keys move the divider the way it looks: right (left in right-to-left) or down is up the scale.
  • A press anywhere on the picture moves focus to the handle, so the keys carry on from there. The handle has a visible focus ring.
  • Touch drags along the divider's axis and still scrolls the page across it. Each pointer is captured, so a drag keeps going outside the picture.
  • Give each { src, alt } image an alt that says what is different about it. disabled takes the handle out of the tab order and sets aria-disabled.