Animate with clip-path

Animate clip-path: inset() for tabs, sliders and wipes: no layout.

Emil Kowalski@emilkowalski
The Magic of Clip Path
Jul 9, 2024View on X ↗
Emil Kowalski@emilkowalski
Toggle your theme using clip-path
Nov 16, 2022View on X ↗
Emil Kowalski@emilkowalski
A mix of clip path and CSS transitions
Mar 17, 2025View on X ↗
Emil KowalskiThe Magic of Clip Path
clip-path: inset(0px 75% 0px 0% round 17px);
emilkowal.skiRead article ↗

From emil-design-eng and his clip-path article.

Tabs: clipped copy

Click a tab in slow motion; watch the label color.

Pill + color fadeBad
transition: color, transform
Clipped duplicateGood
clip-path: inset(0 75% 0 0% round 17px)

<div class="tabs">
  <div class="list">…buttons…</div>
  <div class="list active" aria-hidden="true">…same labels…</div>
</div>

.list.active {
  position: absolute; inset: 0;
  background: var(--accent); color: var(--accent-ink);
  pointer-events: none;
  clip-path: inset(0 75% 0 0% round 17px);
  transition: clip-path 250ms var(--ease-out);
}

const left = (i * 100) / n, right = ((n - 1 - i) * 100) / n;
active.style.clipPath = `inset(0 ${right}% 0 ${left}% round 17px)`;

Comparison slider

Drag either divider; watch the left text rewrap.

Resize the top layerBad
After

Revenue grew 18% after the pricing change

Before

Revenue grew 18% after the pricing change

width: 50%
Clip the top layerGood
After

Revenue grew 18% after the pricing change

Before

Revenue grew 18% after the pricing change

clip-path: inset(0 50% 0 0)
.before {
  position: absolute; inset: 0;
  clip-path: inset(0 calc(100% - var(--pos)) 0 0);
}

slider.addEventListener("pointerdown", (e) => {
  slider.setPointerCapture(e.pointerId);
  move(e);
});
slider.addEventListener("pointermove", (e) => {
  if (slider.hasPointerCapture(e.pointerId)) move(e);
});
function move(e) {
  const r = slider.getBoundingClientRect();
  const pct = Math.min(100, Math.max(0, ((e.clientX - r.left) / r.width) * 100));
  slider.style.setProperty("--pos", `${pct}%`);
}

Theme wipe

Press a toggle, then press again mid-wipe.

Transition the colorsBad
Invoices3 due
  • Acme Co$2,400
  • Globex$980
  • Initech$1,150
transition: all 300ms ease
Clip a themed copyGood
Invoices3 due
  • Acme Co$2,400
  • Globex$980
  • Initech$1,150
clip-path: circle(r at x y)

.wipe {
  position: absolute; inset: 0;
  transition: clip-path 300ms var(--ease-in-out);
}

const r = Math.hypot(Math.max(x, w - x), Math.max(y, h - y));
wipe.style.clipPath = `circle(${r}px at ${x}px ${y}px)`;
wipe.addEventListener("transitionend", () => {
  base.dataset.theme = next;
  resetWipe();
});

Why it matters, pros and cons

Why it matters

A clip only picks which pixels paint: nothing reflows, color flips at the edge.

Pros

  • No layout shift, no text rewrap.
  • Color changes at the edge, one curve.
  • A plain transition, so it retargets mid-flight.

Cons

  • Duplicate DOM: hide the copy from assistive tech.
  • Clips shadows and focus rings outside the box.
  • inset() to circle() jumps; keep one shape function.