# Before / After

> Compare wireframe vs shipped UI mockups.

A shadcn/ui block from the [@atroui](https://www.atroui.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/atroui/before-after-slider).

- Page, with a live preview: https://shadcn.exchange/r/atroui/before-after-slider
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/atroui/before-after-slider-light-fa7679405e3e77ec.png
- Registry item: https://www.atroui.com/r/before-after-slider.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://www.atroui.com/r/before-after-slider.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@atroui/utils`, `@atroui/mockup-frame`

## Files

### `registry/default/blocks/before-after-slider.tsx`

```tsx
"use client";

import { useCallback, useRef, useState } from "react";

import { UiMockupFrame, type MockupVariant } from "../ui/mockup-frame";
import { cn } from "@/lib/utils";

type BeforeAfterSliderProps = {
  variant?: MockupVariant;
  beforeLabel?: string;
  afterLabel?: string;
  className?: string;
};

export function BeforeAfterSlider({
  variant = "saas",
  beforeLabel = "Before",
  afterLabel = "After",
  className,
}: BeforeAfterSliderProps) {
  const [position, setPosition] = useState(50);
  const containerRef = useRef<HTMLDivElement>(null);
  const dragging = useRef(false);

  const updatePosition = useCallback((clientX: number) => {
    const el = containerRef.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    const pct = Math.min(100, Math.max(0, ((clientX - rect.left) / rect.width) * 100));
    setPosition(pct);
  }, []);

  const onPointerDown = (e: React.PointerEvent) => {
    dragging.current = true;
    (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
    updatePosition(e.clientX);
  };

  const onPointerMove = (e: React.PointerEvent) => {
    if (!dragging.current) return;
    updatePosition(e.clientX);
  };

  const onPointerUp = () => {
    dragging.current = false;
  };

  const onKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "ArrowLeft") setPosition((p) => Math.max(0, p - 5));
    if (e.key === "ArrowRight") setPosition((p) => Math.min(100, p + 5));
  };

  return (
    <div
      ref={containerRef}
      className={cn(
        "relative aspect-[16/9] overflow-hidden rounded-2xl border border-border-subtle bg-muted p-3 sm:p-4 select-none touch-none",
        className
      )}
      onPointerMove={onPointerMove}
      onPointerUp={onPointerUp}
      onPointerLeave={onPointerUp}
    >
      <div className="absolute inset-3 sm:inset-4" aria-hidden>
        <UiMockupFrame variant={variant} state="shipped" label={`${afterLabel} UI state`} className="h-full" />
      </div>

      <div
        className="absolute inset-3 sm:inset-4 overflow-hidden rounded-lg"
        style={{ clipPath: `inset(0 ${100 - position}% 0 0)` }}
        aria-hidden
      >
        <UiMockupFrame variant={variant} state="wireframe" label={`${beforeLabel} UI state`} className="h-full" />
      </div>

      <div
        className="absolute top-3 bottom-3 z-10 w-1 cursor-ew-resize bg-white shadow-lg sm:top-4 sm:bottom-4"
        style={{ left: `calc(${position}% * 0.92 + 4%)`, transform: "translateX(-50%)" }}
        onPointerDown={onPointerDown}
        onKeyDown={onKeyDown}
        role="slider"
        tabIndex={0}
        aria-valuenow={Math.round(position)}
        aria-valuemin={0}
        aria-valuemax={100}
        aria-label={`Compare ${beforeLabel} and ${afterLabel}. Use arrow keys or drag.`}
      >
        <div className="absolute top-1/2 left-1/2 flex size-9 items-center justify-center border-2 border-white bg-brand shadow-md">
          <span className="text-[10px] font-bold text-white" aria-hidden>
            ||
          </span>
        </div>
      </div>

      <div className="pointer-events-none absolute bottom-6 left-6 border border-border-subtle bg-background/90 px-2.5 py-1 text-[10px] font-medium backdrop-blur-sm">
        {beforeLabel}
      </div>
      <div className="pointer-events-none absolute right-6 bottom-6 border border-border-subtle bg-background/90 px-2.5 py-1 text-[10px] font-medium backdrop-blur-sm">
        {afterLabel}
      </div>
    </div>
  );
}
```
