Before / After Slider

Drag the divider across the picture to compare the edited version with the original underneath.

  • JS
  • Images
  • Drag
  • Added
BeforeAfter

AI prompt

Build an accessible before/after image comparison in React. Stack two images of the same size; the "after" image is on top and clipped with clip-path: inset(0 calc(100% - var(--pos) * 1%) 0 0). Put a native <input type="range"> (0–100, aria-label "Compare before and after") over the whole picture with opacity 0 so it handles dragging, touch and arrow keys, and write its value into --pos. Draw a 2px divider line and a round 40px handle at left: calc(var(--pos) * 1%), with two small chevrons inside. Show "Before" and "After" tags in the corners that fade out when the divider covers them. When the range is focused with the keyboard, show a focus ring on the handle. Start at 50%. No transitions on the clip itself, so it tracks the pointer exactly.

Source

Demo.tsx
'use client'

import { useState } from 'react'
import s from './demo.module.css'

export default function BeforeAfter({ labels }: { labels: Record<string, string> }) {
  const [pos, setPos] = useState(50)

  return (
    <div className={s.frame} style={{ '--pos': pos } as React.CSSProperties}>
      <div className={`${s.scene} ${s.before}`} aria-hidden="true" />
      <div className={`${s.scene} ${s.after}`} aria-hidden="true" />
      <span className={`${s.tag} ${s.tagBefore}`} data-hidden={pos < 22 || undefined}>
        {labels.before}
      </span>
      <span className={`${s.tag} ${s.tagAfter}`} data-hidden={pos > 78 || undefined}>
        {labels.after}
      </span>
      <span className={s.divider} aria-hidden="true">
        <span className={s.handle}>‹ ›</span>
      </span>
      <input
        type="range"
        min={0}
        max={100}
        value={pos}
        aria-label={labels.label}
        className={s.input}
        onChange={(e) => setPos(Number(e.target.value))}
      />
    </div>
  )
}
demo.module.css
.frame {
  /* scene palette: a CSS-drawn landscape stands in for a photo */
  --sky-top: oklch(78% 0.08 70);
  --sky-low: oklch(70% 0.13 50);
  --sun: oklch(92% 0.09 85);
  --hill-far: oklch(48% 0.07 40);
  --hill-near: oklch(30% 0.04 45);
  position: relative;
  width: min(300px, 100%);
  aspect-ratio: 3 / 2;
  max-width: 100%;
  overflow: hidden;
  border-radius: 14px;
  user-select: none;
}

.scene {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 70% 40% at 25% 105%, var(--hill-near) 98%, transparent 100%),
    radial-gradient(ellipse 80% 45% at 85% 100%, var(--hill-far) 98%, transparent 100%),
    radial-gradient(circle at 68% 42%, var(--sun) 0 11%, transparent 11.5%),
    linear-gradient(180deg, var(--sky-top), var(--sky-low));
}

.before { filter: grayscale(1) contrast(0.85) brightness(0.9); }
.after { clip-path: inset(0 calc(100% - var(--pos) * 1%) 0 0); }

.tag {
  position: absolute;
  top: 10px;
  padding: 4px 8px;
  border-radius: 6px;
  background: var(--stage-bg);
  color: var(--stage-ink);
  font: 500 var(--text-xs) / 1 var(--font-mono);
  transition: opacity 150ms linear;
}

.tagAfter { left: 10px; }
.tagBefore { right: 10px; }
.tag[data-hidden] { opacity: 0; }

.divider {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--pos) * 1%);
  width: 2px;
  margin-left: -1px;
  background: var(--stage-ink);
  pointer-events: none;
}

.handle {
  position: absolute;
  top: 50%;
  left: 50%;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--stage-ink);
  color: var(--stage-bg);
  font: 700 var(--text-base) / 1 var(--font-body);
  transform: translate(-50%, -50%);
}

.input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
}

.input:focus-visible ~ .divider .handle,
.frame:has(.input:focus-visible) .handle { outline: 2px solid var(--color-focus); outline-offset: 3px; }

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.