Skip to content

Clip-Path

clip-path hides part of an element without touching its layout. The box keeps its size and the text keeps its wrapping. Only the visible window changes.

Wrong

Deploy finished

Live at acme.com

Right

Deploy finished

Live at acme.com

The left card animates its width, so its text gets squeezed into an ellipsis on every frame. The right card is clipped, so it wipes away in one piece.

Animate the window, not the box. A width change makes the browser recalculate layout on every frame. A clip change skips layout, but most browsers still repaint the clipped element each frame (Chrome can run basic shapes like inset() off the main thread). That is cheap for a small, one-off wipe. For a large or frequent reveal, slide two layers with transform instead: an outer one that clips and an inner one that moves the opposite way. See Smooth Animation.

Tabs

A segmented control has a pill that slides between options, and the label under it changes color. If you transition each label's color, one label fades out while the next fades in, and for a moment both are half gray.

Instead, render the tabs twice. The copy on top has inverted colors and is clipped down to the pill. Then animate the clip.

Wrong
Right

The text on the right switches color exactly at the pill's edge. The segmented controls on this site work the same way.

Hold to delete

A destructive action can ask for a short hold. Render the red state on top, clipped to zero width, and reveal it while the button is held.

The reveal runs for 1.5s with a linear curve, so the progress reads as time. Letting go snaps it back in 200ms with an ease-out. Nobody wants to wait for a cancel.

Comparison slider

Stack two images of the same size and clip the top one. Moving the clip edge gives you a before and after slider. Neither image moves or stretches.

Water Lilies by Claude Monet, in grayscale
Water Lilies by Claude Monet, in color

Usage

app.tsx
<div
  class="transition-[clip-path] duration-500 ease-[cubic-bezier(0.23,1,0.32,1)] [clip-path:inset(0_0_0_0)] data-hidden:[clip-path:inset(0_100%_0_0)]"
>
  ...
</div>
.card {
  /* top right bottom left */
  clip-path: inset(0 0 0 0);
  transition: clip-path 500ms cubic-bezier(0.23, 1, 0.32, 1);
}

.card[data-hidden] {
  clip-path: inset(0 100% 0 0);
}

clip-path cuts off everything outside the box, including shadows and focus rings. Negative values give them room. The card in the first demo goes from inset(-16px) to inset(-16px calc(100% + 16px) -16px -16px).

Resources

Search concepts

Search for a command to run...