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?” clip-path defines a clipping region; what falls inside is visible and what falls outside is hidden. The property that makes it an animation tool is the one he states next: it has no effect on layout. An element with a clip-path occupies exactly the space it would without one, “just like transform.” That single fact is why it beats the obvious alternatives, because you can animate it without the browser re-laying out the page and without the content around it moving.
The coordinate system starts at the top left. A circle is written as circle(50% at 50% 50%), a radius of half the element positioned at its centre, and there are ellipse, polygon, and url() shapes as well, but the post spends most of its time on inset(), whose four values are the top, right, bottom, and left offsets of a rectangle. inset(100%) hides the whole element; inset(0px 50% 0px 0px) hides its right half. From there 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 he prefers to the two-divs-with-overflow approach because it is hardware-accelerated and needs no extra DOM. The text variant hides the bottom half of a dashed word and the top half of a solid one and moves the split with the mouse. “It’s just a matter of creativity.”
The image reveal is the one you will reuse most. The image starts fully clipped, inset(0 0 100% 0), and animates to inset(0 0 0 0) over one second with cubic-bezier(0.77, 0, 0.175, 1), the In Out Quart curve from 2.2. You could animate height instead, he says, but clip-path is hardware-accelerated, and because the image is already there and only clipped, revealing it causes no layout shift. To run it on scroll he uses Framer Motion’s useInView with { once: true, margin: "-100px" }, so it fires once and only when at least 100px of the image is in view, and drives the animation through WAAPI, element.animate(), to keep the animation logic in one place. If you are not already using Framer Motion, he suggests Intersection Observer instead, because the library is heavy. The scroll-progress line in the same post is the same trick at a different scale: a div clipped from the bottom, with useScroll mapped through useTransform from 100% to 0%, so it looks like an SVG being drawn.
Try the reveal below. Press Toggle and watch the edge of the shape travel across the panel; nothing is fading, the panel is being uncovered in place. Switch the shape between inset, circle, and polygon to see that the same property animates all three. Then turn on the opacity comparison and watch the same panel dissolve, with nothing for the eye to follow.
Two more uses show why he calls the property underrated. The tabs transition solves a problem that 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 the duplicate to the active tab, and animate the clip on change; the demo’s value reads clip-path: inset(0px 75% 0px 0% round 17px), where 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 the technique to a tweet by Paco Coursey and recommends Radix Tabs underneath for accessibility. In Good vs Great Animations he revisits it as an example of knowing your tools: slow the tabs animation down and you can see the highlight and the color change fail to agree; clip-path is what makes them agree.
The other is hold-to-delete, which his skill file lists as a clip-path pattern and his course has as an exercise. A fill is clipped out, and on :active it transitions to inset(0 0 0 0) over 2s with linear timing; on release it snaps back with 200ms ease-out. Read against his own flowchart, the linear fill is constant motion, a progress indicator, and the fast ease-out return is the system responding. Back in 2021 he also used the reveal keyframes to animate a theme switch by duplicating the whole page, which he calls hacky, and notes the View Transitions API now does the same without duplication. Once you know the trick, he says, you see it everywhere: Vercel’s security page uses it, and Tuple’s comparison could.
The practical rule is short. When something needs to be revealed, wiped, swept, or filled, and the layout around it must not move, reach for clip-path before height, width, or opacity. Pair it with the same strong ease-in-out curve for reveals, linear for progress, and keep the DOM you already have.