Skip to content
Knsept
Free playbook

Before/after slider

Drag to compare two images. Built on a native range input, so mouse, touch and keyboard all work for free.

CSSJavaScriptUI Widgets Updated Oct 23, 2026
Live demo Reduced motion is on, so effects are toned down
<figure class="compare" style="--position: 50%">
  <!-- The scene is drawn once and reused, so both sides always line up. -->
  <svg width="0" height="0" aria-hidden="true">
    <defs>
      <linearGradient id="sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ff9a5a"/><stop offset=".55" stop-color="#ffcf7a"/><stop offset="1" stop-color="#7ab8ff"/></linearGradient>
      <symbol id="scene" viewBox="0 0 400 240">
        <rect width="400" height="240" fill="url(#sky)"/>
        <circle cx="300" cy="92" r="34" fill="#fff4c9"/>
        <path d="M0 170 L80 95 L150 160 L230 70 L320 150 L400 105 V240 H0Z" fill="#3b5b8a"/>
        <path d="M0 195 L110 140 L200 190 L290 135 L400 180 V240 H0Z" fill="#253f66"/>
        <rect y="205" width="400" height="35" fill="#1b2f4d"/>
        <path d="M40 222h70M180 216h90M300 226h60" stroke="#7ab8ff" stroke-width="3" stroke-linecap="round" opacity=".6"/>
      </symbol>
    </defs>
  </svg>

  <svg class="compare-after" viewBox="0 0 400 240" role="img" aria-label="Edited photo"><use href="#scene"/></svg>
  <svg class="compare-before" viewBox="0 0 400 240" role="img" aria-label="Original photo"><use href="#scene"/></svg>

  <span class="compare-label before-label">Before</span>
  <span class="compare-label after-label">After</span>
  <span class="compare-handle" aria-hidden="true"></span>

  <input class="compare-range" type="range" min="0" max="100" value="50" aria-label="Compare before and after">
</figure>

Under the image sits a native <input type="range">, invisible but focusable. It gives you arrow-key support and screen-reader announcements ("40% before, 60% after") without writing any of it. Pointer dragging is handled separately with pointer events, because some mobile browsers only let you drag a range by its tiny thumb. Both paths write the same value, and the script copies it into a CSS variable.

The "before" image sits on top and is clipped with clip-path: inset(), which hides everything to the right of --position. The handle is a 2px line at the same position. touch-action: pan-y lets vertical swipes still scroll the page on phones.

Use real images: swap the two <svg> elements for <img> tags with the same classes. This demo draws one scene and shows it twice, with a grayscale filter on the "before" side, so it needs no image files.

More from the library

Images & media live

Scroll-snap carousel

A swipeable carousel built on native scrolling and CSS scroll snap, with arrow buttons and dots that track the current slide.

Navigation live

Smooth accordion

A FAQ accordion built on native details/summary, with a smooth open and close animation and an optional one-open-at-a-time mode.

Scroll & motion live

Count-up stats

Numbers that count up when they scroll into view, with easing and formatting. A quicker count with reduced motion.