Before / After
atroui/before-after-sliderFreeBlock
Compare wireframe vs shipped UI mockups.
Install it
npx shadcn@latest add https://atroui.com/r/before-after-slider.jsonSource
1"use client";23import { useCallback, useRef, useState } from "react";45import { UiMockupFrame, type MockupVariant } from "../ui/mockup-frame";6import { cn } from "@/lib/utils";78type BeforeAfterSliderProps = {9 variant?: MockupVariant;10 beforeLabel?: string;11 afterLabel?: string;12 className?: string;13};1415export function BeforeAfterSlider({16 variant = "saas",17 beforeLabel = "Before",18 afterLabel = "After",19 className,20}: BeforeAfterSliderProps) {21 const [position, setPosition] = useState(50);22 const containerRef = useRef<HTMLDivElement>(null);23 const dragging = useRef(false);2425 const updatePosition = useCallback((clientX: number) => {26 const el = containerRef.current;27 if (!el) return;28 const rect = el.getBoundingClientRect();29 const pct = Math.min(100, Math.max(0, ((clientX - rect.left) / rect.width) * 100));30 setPosition(pct);31 }, []);3233 const onPointerDown = (e: React.PointerEvent) => {34 dragging.current = true;35 (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);36 updatePosition(e.clientX);37 };3839 const onPointerMove = (e: React.PointerEvent) => {40 if (!dragging.current) return;41 updatePosition(e.clientX);42 };4344 const onPointerUp = () => {45 dragging.current = false;46 };4748 const onKeyDown = (e: React.KeyboardEvent) => {49 if (e.key === "ArrowLeft") setPosition((p) => Math.max(0, p - 5));50 if (e.key === "ArrowRight") setPosition((p) => Math.min(100, p + 5));51 };5253 return (54 <div55 ref={containerRef}56 className={cn(57 "relative aspect-[16/9] overflow-hidden rounded-2xl border border-border-subtle bg-muted p-3 sm:p-4 select-none touch-none",58 className59 )}60 onPointerMove={onPointerMove}61 onPointerUp={onPointerUp}62 onPointerLeave={onPointerUp}63 >64 <div className="absolute inset-3 sm:inset-4" aria-hidden>65 <UiMockupFrame variant={variant} state="shipped" label={`${afterLabel} UI state`} className="h-full" />66 </div>6768 <div69 className="absolute inset-3 sm:inset-4 overflow-hidden rounded-lg"70 style={{ clipPath: `inset(0 ${100 - position}% 0 0)` }}71 aria-hidden72 >73 <UiMockupFrame variant={variant} state="wireframe" label={`${beforeLabel} UI state`} className="h-full" />74 </div>7576 <div77 className="absolute top-3 bottom-3 z-10 w-1 cursor-ew-resize bg-white shadow-lg sm:top-4 sm:bottom-4"78// … truncated
What it pulls in
Other registry items
Files it writes
More from atroui
All 82 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Analytics Provideranalytics-provider | atroui | Blocks | Free | 1 dep | |
| AR Portfolioar-portfolio | atroui | Blocks | Free | 1 dep | |
| Calendly Embedcalendly-embed | atroui | Blocks | Free | 1 dep | |
| Changelogchangelog | atroui | Blocks | Free | no deps | |
| Command Menucommand-menu | atroui | Blocks | Free | 4 deps | |
| Contact Formcontact-form | atroui | Blocks | Free | 2 deps | |
| Contextual CTAcontextual-cta | atroui | Blocks | Free | 2 deps | |
| Count Upcount-up | atroui | Blocks | Free | no deps |
