# 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).

> **This is an example, not the only option.** It is one component built on the dithercn engine. Install it when it is what you were asked for. For any other look, don't stretch its props to fit: write your own field on `DitherShader` ([Write your own dither](/docs/engine/write-your-own.md)), and read this example's source for technique. Its full source is in [https://dithercn.dev/r/image-compare.json](https://dithercn.dev/r/image-compare.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://dithercn.dev/r/image-compare.json
```

## 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.

```tsx title="examples/image-compare-presets.tsx"
import { ImageCompare } from "@/components/dithercn/image-compare";

export default function ImageComparePresets() {
  return (
    <ImageCompare
      src="/examples/aldrin.jpg"
      alt="Buzz Aldrin on the Moon, as newsprint on the left and Game Boy greens on the right"
      before={{ kind: "dither", preset: "newsprint" }}
      after={{ kind: "dither", preset: "gameboy" }}
      beforeLabel="Newsprint"
      afterLabel="Game Boy"
      defaultValue={42}
      ratio={4 / 5}
      className="w-full max-w-sm rounded-xl"
    />
  );
}
```

### Feature section [#feature-section]

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

```tsx title="examples/image-compare-feature.tsx"
import { ImageCompare } from "@/components/dithercn/image-compare";

export default function ImageCompareFeature() {
  return (
    <section className="grid w-full max-w-3xl items-center gap-8 sm:grid-cols-[1fr_1.2fr]">
      <div className="flex flex-col gap-3">
        <span className="text-primary font-mono text-xs tracking-[0.14em] uppercase">
          Brand kit
        </span>
        <h3 className="text-2xl font-medium tracking-tight text-balance">
          Every photo, in your house style
        </h3>
        <p className="text-muted-foreground text-sm leading-relaxed text-pretty">
          Drop in any photo and it comes out in two brand colours, with the same
          grain as the rest of the site. Drag the divider to see what changed.
        </p>
      </div>
      <ImageCompare
        src="/examples/earthrise.jpg"
        alt="Earthrise from Apollo 8, in brand duotone on the left and the original on the right"
        before={{ color: "purple", kind: "dither", preset: "duotone" }}
        beforeLabel="Brand"
        defaultValue={58}
        ratio={4 / 3}
        className="w-full rounded-xl"
      />
    </section>
  );
}
```

## 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.