# Image compare

A draggable before and after slider: the dithered image on one side, the original or another dither on the other.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. The dithercn agent skill, for building any dither on the engine, is at [/.well-known/agent-skills/dithercn/SKILL.md](/.well-known/agent-skills/dithercn/SKILL.md).

<ComponentPreview src="examples/image-compare-demo.tsx" size="lg" controls="imageCompareControls">
  <ImageCompareDemo />
</ComponentPreview>

## Installation [#installation]

<InstallBlock name="image-compare" />

## Usage [#usage]

```tsx
import { ImageCompare } from "@/components/dithercn/image-compare";

export function Showcase() {
  return (
    <ImageCompare
      src="/photos/launch.jpg"
      alt="The launch pad at dawn, dithered and original"
      className="w-full rounded-xl"
    />
  );
}
```

Drag the divider, click anywhere on the image to send it there, or tab to the handle and use the arrow keys. By default the left side is dithered with the `duotone` preset and the right side is the original photo. When the image first comes into view, the divider nudges left and right once so readers see that it moves.

Each side is either `{ kind: "original" }` or `{ kind: "dither", ...look }`, where the look takes the same props as [Dither image](/docs/images/dither-image): `preset`, `color`, `palette`, `method`, `cell` and the rest.

## Examples [#examples]

### Two dither looks [#two-dither-looks]

Both sides can be dithered. Here newsprint meets Game Boy greens.

<ComponentPreview src="examples/image-compare-presets.tsx" size="lg">
  <ImageComparePresets />
</ComponentPreview>

### Feature section [#feature-section]

A landing page block that shows what the brand treatment does to a photo.

<ComponentPreview src="examples/image-compare-feature.tsx" size="lg">
  <ImageCompareFeature />
</ComponentPreview>

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `src` (required) | `string` |  | Image URL. Cross-origin images need CORS headers. |
| `alt` (required) | `string` |  | Describes the image and the comparison. Used as the group's label. |
| `before` | `{ kind: "original" } \| { kind: "dither", ...DitherLook }` | `{ kind: "dither" }` | The left side. |
| `after` | `{ kind: "original" } \| { kind: "dither", ...DitherLook }` | `{ kind: "original" }` | The right side. |
| `beforeLabel` | `string` | `"Dithered" or "Original"` | Label on the left side, also used in the slider's name. |
| `afterLabel` | `string` | `"Dithered" or "Original"` | Label on the right side. |
| `labels` | `boolean` | `true` | Show the corner labels. They fade out when the divider covers them. |
| `value` | `number` |  | Divider position from the left, 0 to 100. Makes the slider controlled. |
| `defaultValue` | `number` | `50` | Starting position when uncontrolled. |
| `onValueChange` | `(value: number) => void` |  | Called on every move, from pointer or keyboard. |
| `hint` | `boolean` | `true` | Nudge the divider once when the image first comes into view. |
| `ratio` | `number` | `The image's ratio` | Width over height. |
| `fit` | `"cover" \| "contain"` | `"cover"` | How the image fills its box, on both sides. |

## Notes [#notes]

* The handle is a `role="slider"` with a value from 0 to 100. Arrow keys move it by 1, Shift and arrow or Page Up and Page Down by 10, Home and End to the ends.
* Clicks glide the divider over 200ms; drags and key presses follow exactly.
* On touch screens a sideways drag moves the divider and a vertical swipe still scrolls the page.
* Under reduced motion there is no hint and no glide.