A before and after comparison: two pictures stacked in one frame, the top one revealed up to a divider with a round handle. Drag the divider, click anywhere to send it there on a spring, or switch to hover mode so a mouse moves it without pressing; it runs sideways or top to bottom. The reveal is a clip-path and the position is a single CSS variable, so nothing reflows and moving costs no renders. Captions on each side fade as the divider reaches them. Both pictures load before either shows, then fade in together over a soft placeholder. The frame keeps its aspect ratio, and the keyboard works it like a slider: arrows, Page Up and Page Down, Home and End.
The import and the props worth knowing about, in one place.
import { LoupeCompare } from "@/components/beste/component/loupe-compare";
<LoupeCompare
before={{ src: "/photos/raw.jpg", alt: "The stage before the lighting cue" }}
after={{ src: "/photos/graded.jpg", alt: "The stage with the lighting cue" }}
labels={{ before: "Raw", after: "Graded" }}
aspectRatio="16/9"
/>
// Top to bottom, following the mouse without a press
<LoupeCompare
before={{ src: "/photos/scan.jpg", alt: "Archive scan", className: "grayscale" }}
after={{ src: "/photos/scan.jpg", alt: "Restored scan" }}
orientation="vertical"
mode="hover"
onValueCommit={(value) => console.log("settled at", value)}
/>Every one of these is in the component already. They are listed because a props table cannot mention a gesture, so nothing else on this page can tell you they exist.
| Drag | Move the divider; it follows the pointer exactly. |
|---|---|
| Click | Send the divider to that spot on a spring. |
| Hover mode | A mouse moves the divider without pressing. |
| Arrow keys | Move it by 1%. On a vertical divider, up moves it up. |
| Page Up / Page Down | Move it by 10%. |
| Home / End | Send it to either edge. |
Read from the component's own type, so this cannot drift from what it accepts.
| Prop | Type | Default | Description |
|---|---|---|---|
before* | CompareImage | — | Shown on the start side of the divider: left, or top when vertical. |
after* | CompareImage | — | Shown on the end side of the divider. |
value | number | — | Divider position from the start edge, 0 to 100. Pair with `onValueChange` to control it. |
defaultValue | number | 50 | |
onValueChange | (value: number) => void | — | Every move of the divider. |
onValueCommit | (value: number) => void | — | Once per gesture: on release, after a click or a key press. |
orientation | "horizontal" | "vertical" | "horizontal" | |
mode | "drag" | "hover" | — | `drag` moves the divider on press; `hover` lets a mouse move it without pressing. @defaultValue "drag" |
aspectRatio | number | string | — | Width to height, as a number or a CSS ratio like "3/2". @defaultValue "3/2" |
labels | { before?: string; after?: string } | false | — | Captions on each side; `false` hides them. |
tone | "muted" | "outline" | "ghost" | "muted" | |
size | "sm" | "default" | "lg" | "default" | |
disabled | boolean | — | |
aria-label | string | "Compare before and after" | |
className | string | — |