Feel · Motion
The magic of clip-path

The magic of clip-path

Emil Kowalski’s The Magic of Clip Path starts from a property most people file under shapes. “clip-path is often used for trimming a DOM node into specific shapes, like triangles. But what if I told you that it’s also great for animations?”

A property that skips layout#

defines a clipping region: inside is visible, outside is hidden. What makes it an animation tool is that it has no effect on layout: a clipped element occupies the same space as an unclipped one, “just like transform,” so animating it moves nothing around it.

The coordinate system starts top left. inset() takes top, right, bottom, and left offsets of a rectangle: inset(100%) hides the whole element, inset(0px 50% 0px 0px) hides its right half.

CSSEmil Kowalski, The Magic of Clip Path
.circle {  clip-path: circle(50% at 50% 50%);}

The examples are all the same move. A comparison slider is two images overlaid, the top one clipped with inset(0 50% 0 0) and the value adjusted as you drag, which beats two divs with overflow hidden because it is hardware-accelerated and needs no extra DOM. The text variant splits a dashed word and a solid one at the mouse position. “It’s just a matter of creativity.”

The image reveal#

The image starts fully clipped and animates open over one second with cubic-bezier(0.77, 0, 0.175, 1), the In Out Quart curve from 2.2. You could animate height, but clip-path is hardware-accelerated, and because the image is already there and only clipped, revealing it causes no layout shift.

CSSEmil Kowalski, The Magic of Clip Path
.image-reveal {  clip-path: inset(0 0 100% 0);  animation: reveal 1s forwards cubic-bezier(0.77, 0, 0.175, 1);}@keyframes reveal {  to {    clip-path: inset(0 0 0 0);  }}

To run it on scroll he uses Framer Motion’s useInView, firing once and only when at least 100px of the image is in view, and drives the animation through WAAPI to keep the logic in one place. If you are not already using Framer Motion, use Intersection Observer; the library is heavy.

TypeScriptEmil Kowalski, The Magic of Clip Path
const isInView = useInView(ref, { once: true, margin: "-100px" });if (isInView && ref.current) {  ref.current.animate(    [{ clipPath: "inset(0 0 100% 0)" }, { clipPath: "inset(0 0 0 0)" }],    {      duration: 1000,      fill: "forwards",      easing: "cubic-bezier(0.77, 0, 0.175, 1)",    },  );}

The scroll-progress line in the same post is the same trick: a div clipped from the bottom, with useScroll mapped through useTransform from 100% to 0%, so it looks like an SVG being drawn. Below, press Toggle and watch the edge travel; nothing fades, the panel is uncovered in place. Then turn on the opacity comparison and watch the same panel dissolve with nothing for the eye to follow.

Progress
100%
Inset bottom
0%
clip-path
hiddenshown

clip-path: inset(0 0 0% 0)

Adjust
Shape
Figure 4. Clip-path reveal. Press Toggle and watch the edge of the shape move, or drag the scrubber to hold any frame. The panel is not fading; it is being uncovered in place, and the clip-path string below is the only thing changing. Turn on the opacity comparison to see the same panel dissolve on the same clock, with nothing for the eye to track.Illustrative numbers

Tabs, theme switch, and a rule#

The tabs transition solves a problem color transitions never quite do: the active tab has a different text color, and animating the color while the highlight slides always feels slightly off. His fix is to duplicate the tab list, style the duplicate as active, clip it to the active tab, and animate the clip on change; round 17px keeps the pill’s corners. The result is seamless, “and we don’t have to worry about timing the color transition, which would never be seamless anyway.” He credits a tweet by Paco Coursey and recommends Radix Tabs underneath for accessibility.

CSSEmil Kowalski, The Magic of Clip Path
clip-path: inset(0px 75% 0px 0% round 17px);

In Good vs Great Animations he revisits it as knowing your tools: slow the tabs down and the highlight and the color change fail to agree; clip-path makes them agree. Back in 2021 he used the same reveal keyframes to animate a theme switch by duplicating the whole page, which he calls hacky; the View Transitions API now does it without duplication. Vercel’s security page uses the trick, and Tuple’s comparison could. The hold-to-delete pattern from his skill file is covered in Friction.

The rule is short. When something must be revealed, wiped, or filled without the layout around it moving, reach for clip-path before height, width, or opacity.