# How to Build Comparison Sliders with clip-path

> Learn to build dynamic before-and-after comparison sliders using CSS clip-path. Update image clips with drag position for smooth, hardware-accelerated comparisons.

- Repository: [Emil Kowalski/skills](https://github.com/emilkowalski/skills)
- Tags: how-to-guide
- Published: 2026-08-03

---

**Use CSS `clip-path: inset()` to dynamically clip an overlay image, updating the right inset percentage based on drag position to create smooth, hardware-accelerated before-and-after comparisons.**

The `emilkowalski/skills` repository documents a performant pattern for building comparison sliders using CSS `clip-path`. According to the design engineering guidelines in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md), this approach leverages GPU acceleration to reveal image overlays without triggering expensive layout recalculations or paint operations.

## Understanding the clip-path Architecture

The comparison slider architecture relies on a two-layer stack where `clip-path` controls visibility. As documented in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) (lines 135-136), `clip-path: inset()` serves as a "powerful animation tool" specifically suited for comparison sliders.

The technique works by placing a "before" image as the base layer and an "after" image positioned absolutely on top. The `clip-path: inset(0 50% 0 0)` property hides a portion of the overlay image—the four values represent top, right, bottom, and left insets. A value of `50%` on the right side clips the overlay exactly in half, while `0%` reveals the full image and `100%` hides it completely.

Because `clip-path` is a layout-independent property, the browser can animate changes entirely on the GPU. This produces 60fps motion without JavaScript-driven reflows, making it ideal for drag-based interactions.

## Building the Slider Structure

### Stacking the Image Layers

Create a container with two images: the base image flows normally in the document, while the overlay uses absolute positioning to cover it completely.

```html
<div class="compare">
  <img src="before.jpg" alt="Before" class="compare__base">
  <img src="after.jpg" alt="After" class="compare__overlay">
  <div class="compare__handle" aria-label="Drag to compare"></div>
</div>

```

```css
.compare {
  position: relative;
  width: 100%;
  height: 400px;
  overflow: hidden;
}

.compare__base {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.compare__overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  clip-path: inset(0 50% 0 0);  /* Start at 50% */
}

```

### Positioning the Drag Handle

The handle sits at the clipping boundary, providing visual feedback and a draggable target. Position it absolutely to match the initial clip value.

```css
.compare__handle {
  position: absolute;
  inset: 0;
  width: 2px;
  background: #fff;
  left: 50%;  /* Matches the 50% inset */
  cursor: ew-resize;
  box-shadow: 0 0 5px rgba(0,0,0,0.3);
}

```

## Implementation Examples

### Pure CSS Foundation

For simple hover-based comparisons, use CSS transitions on the `clip-path` property. When the user hovers over the container, animate the inset to reveal the full overlay.

```css
.compare__overlay {
  clip-path: inset(0 50% 0 0);
  transition: clip-path 0.3s ease-out;
}

.compare:hover .compare__overlay {
  clip-path: inset(0 0 0 0);  /* Reveal full image */
}

```

### JavaScript Drag Implementation

For draggable sliders, update the `clip-path` in real-time based on pointer position. This follows the interaction pattern described in the repository's skill files.

```javascript
const compare = document.querySelector('.compare');
const overlay = compare.querySelector('.compare__overlay');
const handle = compare.querySelector('.compare__handle');

function updateSlider(clientX) {
  const rect = compare.getBoundingClientRect();
  const percent = Math.min(Math.max((clientX - rect.left) / rect.width, 0), 1);
  const insetRight = (1 - percent) * 100;
  
  overlay.style.clipPath = `inset(0 ${insetRight}% 0 0)`;
  handle.style.left = `${percent * 100}%`;
}

let dragging = false;

compare.addEventListener('pointerdown', (e) => {
  dragging = true;
  updateSlider(e.clientX);
});

window.addEventListener('pointermove', (e) => {
  if (dragging) updateSlider(e.clientX);
});

window.addEventListener('pointerup', () => {
  dragging = false;
});

```

The `setPosition` function calculates the horizontal percentage within the container, clamps it between 0 and 1, then maps that to the right inset value. As the user drags left to right, the `clipPath` string updates dynamically, revealing more of the overlay image.

### Web Animations API Approach

According to [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) (lines 13-21), the Web Animations API (WAAPI) provides a declarative way to programmatically animate the clip path without continuous drag loops.

```javascript
function animateReveal(targetPercent) {
  const overlay = document.querySelector('.compare__overlay');
  const current = overlay.style.clipPath || 'inset(0 50% 0 0)';
  const target = `inset(0 ${100 - targetPercent}% 0 0)`;
  
  overlay.animate([
    { clipPath: current },
    { clipPath: target }
  ], {
    duration: 600,
    easing: 'cubic-bezier(0.77, 0, 0.175, 1)'
  }).onfinish = () => {
    overlay.style.clipPath = target;
  };
}

```

This method creates a smooth transition between any two clip states, useful for programmatic comparisons or "reveal" buttons.

## Accessibility and Performance Considerations

Respect user motion preferences by providing a static fallback. The repository's accessibility notes in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) (lines 27-31) recommend disabling animations when `prefers-reduced-motion` is set.

```css
@media (prefers-reduced-motion: reduce) {
  .compare__overlay {
    transition: none !important;
    clip-path: inset(0 25% 0 0) !important;  /* Static 25% reveal */
  }
  .compare__handle {
    display: none;
  }
}

```

The `clip-path` property enjoys excellent browser support and animates efficiently because it operates on the compositor thread. Unlike `width` or `left` changes, modifying `clip-path` does not trigger layout recalculations or repaints, ensuring consistent frame rates even on lower-end devices.

## Summary

- **Stack two images**: Place the "after" image absolutely on top of the "before" image.
- **Use `clip-path: inset()`**: Control visibility by adjusting the right inset value from 0% (full reveal) to 100% (fully hidden).
- **Update on drag**: Map the mouse or touch position to the inset percentage for real-time comparisons.
- **Leverage GPU acceleration**: The browser animates `clip-path` on the compositor thread for smooth 60fps performance.
- **Reference**: Implementation details are documented in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) and [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md).

## Frequently Asked Questions

### Why use `clip-path` instead of `width` or `opacity`?

`clip-path` creates a hard edge between two images without affecting layout. Unlike changing `width`, which triggers layout recalculations, or `opacity`, which creates ghosting effects, `clip-path` simply masks the element. As noted in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md), this property animates on the GPU, providing smoother performance than dimension-based alternatives.

### How do I make the comparison slider accessible?

Include ARIA labels on the drag handle to describe its function, and respect `prefers-reduced-motion` by providing a static view. According to [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) (lines 27-31), you should disable the drag interaction and display a fixed comparison state when users have reduced motion enabled.

### Can I animate the clip-path transition?

Yes. The `clip-path` property supports CSS transitions and the Web Animations API. You can animate between different `inset()` values to create smooth reveals. The repository recommends using `cubic-bezier` easing functions for natural motion, as implemented in the WAAPI example (lines 13-21).

### Does this technique work with content other than images?

Absolutely. The `clip-path` approach works on any rectangular DOM element, including video elements, divs with background images, or complex component layouts. The technique documented in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) specifically mentions that `clip-path` works on "any rectangular element and does not require extra DOM nodes."