motion-image-compare
Before/after slider with a draggable, spring-damped handle. Drop two images into the before and after slots.
Drag, click, or use the arrow keys to reveal.
Preview: Horizontal
Preview: Vertical
Import
import 'motion-components/motion-image-compare' // just this one
import 'motion-components' // or the whole library
Usage
<motion-image-compare start="50">
<img slot="before" src="before.jpg" alt="Before retouch" />
<img slot="after" src="after.jpg" alt="After retouch" />
</motion-image-compare>
Sizing
The host fills its container's width and clips overflow. Give it an explicit height (or use an aspect-ratio container). Slotted images stretch to 100% × 100% with object-fit: cover.
motion-image-compare {
width: 100%;
aspect-ratio: 16 / 9;
border-radius: 12px;
}
Properties
| Attribute | Property | Type | Default | Description |
| start | start | number | 50 | Initial split position as a percentage (0–100) |
| orientation | orientation | "horizontal" | "vertical" | "horizontal" | Drag axis |
| bounce | bounce | number | 0.25 | Spring bounciness of the snap-to-cursor animation |
| duration | duration | number | 0.45 | Spring duration of the snap-to-cursor animation, in seconds |
Slots
| Slot | Type | Default | Description |
| before | element | — | The left/top image (visible behind the clip) |
| after | element | — | The right/bottom image (revealed by dragging) |
CSS custom properties
| Property | Type | Default | Description |
| --mc-color-accent | color | #2563eb | Color of the keyboard focus ring on the knob |
JavaScript properties
| Property | Description |
| position | Split position as a percentage (0 to 100). Reading it gives where the split is going. Setting it springs the split there without firing motion-change. Set before the first render, it sets the position the split starts at. |
Events
| Event | Detail | Description |
| motion-change | position | Fired when a drag, click or key moves the split. position is the percentage it moves to. Not fired when you set position. Bubbles and is composed. |
Other components also fire a bubbling motion-change. A listener on a parent element should check event.target.
import type { MotionImageCompare } from 'motion-components'
const compare = document.querySelector<MotionImageCompare>('motion-image-compare')!
compare.position = 75 // springs to 75%, no event
compare.addEventListener('motion-change', (event) => {
if (event.target !== compare) return
console.log('split at', event.detail.position)
})
Accessibility
The handle is a focusable role="slider" labelled "Image comparison", with aria-valuemin/max/now.
Use ← / → (or ↑ / ↓ in vertical mode) to nudge the split by 2%, hold Shift for 10% jumps. Home and End move it to 0% and 100%.
Slotted images are kept as the source of truth; remember to write meaningful alt text.