Design.dev design.dev
UI Component Prompt

Image Comparison Slider

A draggable before/after slider with smooth pointer, touch, and full keyboard control. Copy the prompt, paste it, ship it.

Copy → Paste → Ship

Live Preview

Vanilla JS · Zero Dependencies
Day Night

Drag the handle, click anywhere on the image, or focus the slider and use arrow keys to compare. Works with mouse, touch, and stylus.

The Prompt

Build a before/after image comparison slider using vanilla HTML, CSS, and JavaScript. No libraries, no frameworks — zero dependencies.

VISUAL DESIGN
- Two images stacked in a fixed-aspect container (16:9 default, configurable)
- Container: border-radius 12px, overflow hidden, box-shadow 0 20px 60px rgba(0,0,0,0.5), background #0a0a0a
- Top image is revealed/hidden via clip-path: inset(0 [100 - position]% 0 0) — positioned by a vertical divider line
- Vertical divider line: 2px wide, white at 0.9 opacity, full height, with a subtle drop shadow for legibility on any image
- Drag handle: 44px circular button centered on the divider, white 2px border, dark translucent background (rgba(10,10,10,0.85)) with backdrop-blur, two 14px chevron icons pointing left and right with a 2px gap between them
- Handle hover/focus: scale to 1.08 with cyan focus ring (0 0 0 4px rgba(0,196,255,0.25))
- Optional corner labels: pill-shaped badges in opposite corners ("Before" top-left, "After" top-right) — pointer-events none so they don't block dragging

INTERACTION
- Pointer Events API only (covers mouse + touch + pen) using setPointerCapture for reliable drag tracking even when the cursor leaves the container
- pointerdown anywhere on the container snaps the slider to that x position AND begins a drag
- pointermove updates the divider position, clamped between 0% and 100%
- pointerup ends the drag and releases pointer capture
- touch-action: none on the container to prevent the browser from hijacking horizontal swipes
- user-select: none on the container to prevent text-selection flicker during drag
- During drag, set data-dragging="true" on the container; CSS uses this to disable any clip-path transition for instant tracking

KEYBOARD ACCESSIBILITY
- Handle is a button with role="slider", aria-valuemin="0", aria-valuemax="100", aria-valuenow updated live, aria-label
- Arrow Left / Arrow Right: move 1% per press
- Shift + Arrow Left/Right: move 10% per press
- Home: jump to 0%, End: jump to 100%
- Page Up / Page Down: ±10%
- Focus visible ring on the handle when keyboard-navigated (use :focus-visible)

LAYOUT & RESPONSIVENESS
- Container fills its parent width up to a max-width
- Aspect ratio held with CSS aspect-ratio property (no JS height calculation)
- Both images use object-fit: cover (or preserveAspectRatio="xMidYMid slice" for inline SVG) so they crop identically
- On screens under 640px: handle shrinks to 38px, label badges shrink to 10px font

API
- Initialize via class hook: any element with data-image-compare auto-upgrades, OR a function: createImageCompare(element, { initial: 50, onChange: (pct) => ... })
- Public methods on the returned controller: setPosition(pct), getPosition(), destroy()
- onChange callback fires with the current percentage (0-100) on every update

ACCESSIBILITY
- prefers-reduced-motion: disable the handle's hover transition
- Both images need meaningful alt text (or aria-hidden if purely decorative)
- The divider and labels are aria-hidden — only the slider button is exposed to assistive tech

OUTPUT
- Single HTML file with embedded <style> and <script>
- Two sample images (or inline SVGs) labeled before/after
- Slider initialized at 50%
- Polished, professional aesthetic — should feel like the comparison sliders on premium image-editing landing pages

Anatomy

01

Stacked Image Layers

Two absolutely-positioned images sharing the same frame, the top one revealed by clip-path inset.

02

Clip-Path Reveal

A single CSS property animates the visible portion — no canvas, no masking image, no JavaScript redraw.

03

Divider Line

A 2px white seam with a soft drop-shadow that reads cleanly over both light and dark imagery.

04

Drag Handle

A circular grip with chevron icons centered on the divider, scaling on hover and focus.

05

Pointer Events

One unified API for mouse, touch, and stylus — with pointer capture for reliable off-container tracking.

06

Click-to-Snap

Tapping anywhere on the image jumps the divider to that position and starts a drag in one motion.

07

Keyboard Slider

Full ARIA slider semantics — arrow keys move 1%, shift jumps 10%, Home/End snap to the edges.

08

Corner Labels

Optional Before/After pill badges in opposing corners with pointer-events none so they never block input.

Usage Guidelines

Use This When

  • Showcasing AI-edited photos, product retouches, or filter effects on image-tool landing pages
  • Documenting design redesigns, dark/light mode comparisons, or wireframe-to-final mockups in case studies
  • Contractors and architects showing before/after renovation, restoration, or build progress

Not Ideal When

  • The two images differ in size, crop, or aspect ratio — alignment will be visually jarring
  • Comparing more than two states — use a tabbed gallery or carousel instead
  • The difference is subtle and only visible side-by-side — a static split layout reads more clearly

Frequently Asked Questions

How do I add a before/after image comparison slider to my website?

Stack two images of identical dimensions in the same container. Use clip-path: inset(0 [100 - position]% 0 0) on the top image so a vertical slice of it is hidden. A draggable vertical handle updates that percentage on pointer events. The prompt above generates the full implementation in vanilla HTML, CSS, and JavaScript with zero dependencies.

Does the slider work on mobile and touch devices?

Yes. The component uses the Pointer Events API, which unifies mouse, touch, and stylus input under one set of handlers (pointerdown, pointermove, pointerup). Combined with touch-action: none on the container, the browser will not hijack horizontal swipes for scrolling, so dragging stays smooth on phones and tablets.

Is the comparison slider accessible to keyboard and screen reader users?

Yes. The handle is a button with role="slider", aria-valuemin="0", aria-valuemax="100", and aria-valuenow updated live. Keyboard support: Arrow Left/Right move 1%, Shift + Arrow moves 10%, Home/End jump to the edges, and Page Up/Down step by 10. The two images carry their own alt text so context is never lost.

Can I use this slider with React, Vue, or any framework?

Absolutely. The output is framework-agnostic vanilla HTML, CSS, and JavaScript, so it drops into any React component (initialize the pointer listeners inside useEffect with a ref), Vue setup() block, Svelte onMount, or plain HTML page. Just remember to clean up pointermove and pointerup listeners on unmount to avoid memory leaks in single-page apps.

Why use CSS clip-path instead of two side-by-side images?

Side-by-side cropping requires JavaScript to recalculate widths on every drag frame and breaks responsively. clip-path animates as a single GPU-accelerated property, scales perfectly with the container, and keeps both images sharp at any aspect ratio. It is also the technique modern image-tool landing pages use for buttery-smooth comparisons.

Can I have multiple comparison sliders on one page?

Yes. Ask the AI to scope every selector and state variable inside an init function that takes the container element as its argument, then call it once per slider. Each instance tracks its own position, pointer ID, and ARIA state independently, so you can stack as many on a single page as your design needs.

Copied to clipboard