Before and after
Two images compared with a slider that works from the keyboard.
Installation
npx shadcn@latest add http://localhost:3000/r/before-after.jsonMKT UI is not published yet, so this points at your local development server. Set NEXT_PUBLIC_REGISTRY_URL to the deployed origin to show the public command.
Usage
"use client";
import { BeforeAfter } from "@/components/before-after";
<BeforeAfter
before={{ src: "/jobs/12-before.jpg", alt: "Corroded copper pipework under the sink", label: "Before" }}
after={{ src: "/jobs/12-after.jpg", alt: "The same run replaced in PEX with new shut-off valves", label: "After" }}
caption="Kitchen repipe, Crestview. Two days on site."
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| before (required) | BeforeAfterImage | — | `{ src, alt, label? }`. The alt text describes the before state on its own. |
| after (required) | BeforeAfterImage | — | `{ src, alt, label? }`. The alt text describes the after state on its own. |
| defaultPosition | number | 50 | Starting split, 0–100. |
| controlLabel | string | "Reveal the before image" | Accessible name for the slider. |
| aspect | "video" | "square" | "wide" | "portrait" | "video" | Frame ratio. Reserves space before the images load. |
| caption | ReactNode | — | Caption under the frame. |
Accessibility
This is the section where a hand-rolled implementation usually goes wrong, so the design is deliberate:
The control is a real <input type="range">. That single decision provides
keyboard operation, a focus ring, and a value announcement for free. A custom pointer
handler would need all three rebuilt, and in practice ships without them — leaving the
comparison unusable for anyone without a mouse.
aria-valuetext says "70% before, 30% after" rather than a bare "70". The number
alone does not say what it is a number of.
Both images are always in the DOM with their own alt text. Someone who cannot see either one still gets both descriptions, which is the actual content of the section.
Arrow keys move the split by 1%, Home and End jump to either end.
Writing the alt text
Describe each state independently, as if the other image did not exist. "Before" and "After" are the labels, not the descriptions.
| Poor | Better |
|---|---|
alt="Before" | alt="Corroded copper pipework under the sink" |
alt="After photo" | alt="The same run replaced in PEX with new shut-off valves" |
Limitations
- Client component — it ships JavaScript.
- Horizontal split only. No vertical wipe or fade.
- Both images must share an aspect ratio, or the comparison misleads.
- No gallery of multiple comparisons yet.