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/reactFor 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.
Description
One illustration twice: the kit's gray on the start side, the theme's hues past the divider.
Drag the handle, or press anywhere on the picture to spring the divider there and keep dragging. The tags in the corners fade as the divider comes close to them.
A sky before and after grading, stacked, with orientation="vertical" and hover.
With hover the divider follows a mouse across the picture with no press; touch and pen still drag. Vertical puts before on top and runs the divider across.
Code
image-compare-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsbefore, after, value, orientation, beforeLabel, afterLabel, hover…
Props
| Prop | Type | |
|---|---|---|
beforeRequired | ImageCompareSideShown on the start side (or the top), up to the divider. A node, or | – |
afterRequired | ImageCompareSideShown past the divider. A node, or | – |
value | numberThe divider's position, 0–100: how much of | – |
defaultValue | numberThe starting position when uncontrolled. default 50 | 50 |
onValueChange | (value: number) => void | – |
orientation | 'horizontal' | 'vertical'
default 'horizontal' | 'horizontal' |
beforeLabel | ReactNodeA tag on the | – |
afterLabel | ReactNodeA tag on the | – |
hover | booleanThe divider follows a mouse without a press. Touch and pen still drag. default false | false |
disabled | booleanBlocks dragging, hovering and keys, and takes the handle out of the tab order. default false | false |
sound | booleanWith sound on ( 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
| Moment | Tokens | ||
|---|---|---|---|
| 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.snappy0.2s, bounce 0 | 0.2s, bounce 0 | |
Value from outsidea new controlled value springs the divider there | spring.snappy0.2s, bounce 0 | 0.2s, bounce 0 | |
| Key stepeach arrow, Page, Home or End step glides | duration.fastease.standard140ms · 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.standard140ms · 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.standard140ms · 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) witharia-orientation, named "Comparison divider". Its value text says how much of each side shows ("Before 60%, After 40%"), usingbeforeLabelandafterLabelwhen 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 analtthat says what is different about it.disabledtakes the handle out of the tab order and setsaria-disabled.