EmpireUI
Get Pro
← Blog8 min read#morphing#css#clip-path

CSS Shape Morphing: clip-path, offset-path and SVG Morph Techniques

Master CSS shape morphing with clip-path animations, offset-path motion trails, and SVG SMIL morphing — with real code examples and browser gotchas explained.

Abstract geometric shapes morphing between colorful fluid CSS animations

Why Shape Morphing Is Harder Than It Looks

Animating a circle into a square sounds trivial. In practice, it breaks in three different ways depending on which API you reach for first. The browser has to interpolate between polygon point counts, path command types, or clip regions — and none of those things are the same data format underneath.

CSS clip-path morphing works when the shapes share the same number of polygon vertices. SVG path morphing works when both paths have identical command sequences. offset-path isn't even about shape-changing — it's about moving an element *along* a shape. Mix those up and you'll spend an afternoon wondering why your transition does nothing at all.

Honestly, half the confusion in tutorials from 2023 and earlier comes from conflating these three completely separate problems. This article keeps them distinct so you can pick the right tool without guessing.

Worth noting: browser support shifted significantly between Chrome 115 and Chrome 120 (released late 2023), with offset-path gaining proper path() function support. If you're testing on anything older than Chromium 116 you'll hit silent failures with no DevTools warning.

clip-path Morphing: Polygons and the Vertex Count Rule

The fundamental constraint is simple: you can only transition between clip-path polygon values that have the same number of points. The browser interpolates each coordinate pair linearly, so polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%) transitions cleanly to polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%) because both have four vertices. That square-to-diamond morph is basically free.

.shape {
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
  transition: clip-path 600ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.shape:hover {
  clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}

What you *can't* do natively is morph a triangle (3 points) into a square (4 points). The interpolation fails silently — you get a jump cut instead of a transition. The workaround is to add a phantom fourth point to the triangle that sits at the midpoint of one of its edges. The shape still looks like a triangle visually but the browser has four coordinates to interpolate.

/* Triangle disguised as 4-point polygon */
.triangle {
  clip-path: polygon(
    50% 0%,
    100% 100%,
    50% 100%, /* phantom midpoint on bottom edge */
    0% 100%
  );
  transition: clip-path 500ms ease;
}

.triangle:hover {
  /* Actual square — same vertex count */
  clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}

One more thing — clip-path: path() is finally widely supported as of 2025 and it's a game-changer for organic shapes. You can drop in actual SVG path data and transition between two path() values, as long as the paths share the same command sequence. Curved blobs? Yes. But you still can't go from an M L L Z path to an M C C Z path in one step.

offset-path: Moving Elements Along Custom Shapes

offset-path is a different beast entirely. It doesn't change the *shape* of an element — it defines a track that the element follows. Think of it like a train on rails where you control the track geometry using CSS or SVG path syntax.

.orbit-dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #7c3aed;
  offset-path: path('M 200 100 A 100 100 0 1 1 199.9 100');
  offset-distance: 0%;
  animation: orbit 2s linear infinite;
}

@keyframes orbit {
  to { offset-distance: 100%; }
}

That gives you a dot circling an ellipse, with zero JavaScript. You can swap the path for a wave, a star, a figure-eight — whatever SVG path you want. The element auto-rotates to follow the path tangent by default (offset-rotate: auto). Disable that with offset-rotate: 0deg if you want the element to maintain its original orientation while it travels.

In practice, the most useful pattern I've seen is decorative blobs trailing around interactive cards — something that fits the fluid aesthetic of glassmorphism components perfectly. The blob never changes shape; it just orbits, which keeps the GPU load negligible compared to a full morph.

Quick aside: offset-path with ray() syntax (e.g., offset-path: ray(45deg closest-side)) lets you animate elements outward from a center point. Useful for explosion/burst effects without any keyframe math.

SVG Path Morphing with SMIL and CSS

SVG's <animate> element (SMIL) is old — dating back to SVG 1.1 in 2003 — but for path morphing it's still the most expressive option available without a JS library. You define from and to values on the d attribute and the browser handles interpolation.

<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
  <path fill="#7c3aed">
    <animate
      attributeName="d"
      dur="1.5s"
      repeatCount="indefinite"
      values="
        M 100,30 L 170,170 L 30,170 Z;
        M 100,10 C 180,10 190,190 100,190 C 10,190 20,10 100,10 Z;
        M 100,30 L 170,170 L 30,170 Z
      "
      calcMode="spline"
      keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"
    />
  </path>
</svg>

The catch is the identical-command-sequence rule again, but SMIL is more forgiving about it across browsers than clip-path: path(). Chrome and Firefox both handle it well as of 2026. Safari occasionally struggles with complex cubic bezier paths that have mismatched command counts, so test there first.

Look, if you need truly arbitrary morphs — any shape to any shape — you're going to reach for a library. GreenSock's MorphSVGPlugin handles command count mismatches by resampling paths. Flubber.js does the same thing in ~10 KB. Neither is magic; they're just triangulating the shapes and interpolating the point clouds underneath.

// Flubber example — morph any path to any path
import { interpolate } from 'flubber';

const interpolator = interpolate(pathA, pathB, { maxSegmentLength: 4 });

// At progress 0-1 during your animation:
const morphedPath = interpolator(0.5); // midpoint shape
element.setAttribute('d', morphedPath);

Combining Techniques: Layered Morph Animations

The best-looking morph animations layer all three techniques. A container element moves along an offset-path track. Its child uses clip-path polygon morphing to cycle between shapes. Behind everything, an SVG blob morphs via SMIL as a background texture. Each layer stays in its lane and none of them fight each other for GPU resources.

@keyframes shapeshift {
  0%   { clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }
  33%  { clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%); }
  66%  { clip-path: polygon(50% 0%, 100% 100%, 0% 100%, 0% 0%); }
  100% { clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }
}

.morphing-card {
  animation: shapeshift 3s ease-in-out infinite;
  will-change: clip-path;
}

That will-change: clip-path line matters more than people realize. Without it, each frame triggers a composite layer promotion at paint time. With it, the browser allocates the layer upfront and the morph stays on the GPU. On a mid-range Android device from 2024, that's the difference between 60 fps and 40 fps.

If you're building this inside a React app, wrap the animated element in a component that only receives the current clip-path value as a prop. Keep the animation logic in a useReducer or a small state machine — not scattered across useEffect calls. Morphing animations are stateful by nature and treating them as fire-and-forget side effects causes jank.

For a full ecosystem of pre-built animated components that already handle this layering correctly, browse the Empire UI library. The gradient generator is also handy for building the vivid backgrounds that make morphing shapes actually pop visually instead of blending into a flat surface.

Performance and Accessibility Checklist

Shape morphing is visually expensive. Not as expensive as box-shadow animations (please never animate box-shadow — use a pseudo-element instead), but clip-path morphing still forces the browser to repaint the clipped region on every frame unless you push it to a compositor layer.

The compositor-safe properties in 2026 are transform, opacity, and filter. clip-path is *not* compositor-safe by default, but will-change: clip-path plus a hardware-accelerated backdrop (like a transform: translateZ(0) on the parent) can force it. Measure with DevTools Performance tab before assuming it's smooth.

/* Accessibility: respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
  .morphing-card,
  .orbit-dot,
  svg path {
    animation: none !important;
    transition: none !important;
  }
}

That media query is non-negotiable. Around 26% of desktop users in the US have some vestibular or motion-sensitivity condition where continuous morphing animations cause real physical discomfort. Kill all motion for them. Your shapes can still exist statically — just don't animate them.

FAQ

Can you morph between clip-path shapes with different numbers of vertices?

Not natively — the transition will jump instead of interpolate. The fix is to add phantom points to the simpler shape so both polygons have equal vertex counts while still looking correct visually.

What's the difference between clip-path morphing and SVG path morphing?

clip-path clips a DOM element's visible area; SVG path morphing changes the actual drawn shape of an SVG path element. They look similar but operate on completely different rendering layers and have different browser constraints.

Is offset-path the same as CSS path animation?

No. offset-path moves an element along a geometric track — it doesn't change the element's shape at all. You're controlling position and rotation over a path, not morphing outlines.

Do I need a JavaScript library for arbitrary SVG morphing?

Yes, if the two paths have different command sequences. Flubber.js or GreenSock MorphSVGPlugin resamples both paths into comparable point clouds. Pure CSS can't handle mismatched path commands.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

CSS Wave Animation: SVG, clip-path and Keyframe TechniquesLiquid Fill Button Animation in CSS: SVG and clip-path MorphCSS clip-path Generator: Polygon, Circle and Inset ShapesAurora Gradient CSS: Three Ways to Build the Effect From Scratch