Pure TailwindMIT licensed

Tailwind Image Clip-Path Transition Hover

Shape morphing where both shapes sit on the same element as two arbitrary clip-path utilities — resting and hovered, one line apart. Since interpolation requires matching vertex counts, having both values visible together is not cosmetic: it is what stops the pair from silently degrading into a hard cut.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="tih-11 w-full min-h-svh grid place-items-center bg-tih11-bg font-tih11-sans px-6 py-10" aria-label="Clip-path shape morph hover demo">
  <div class="grid w-full max-w-195 gap-5 grid-cols-1 sm:grid-cols-3">
    <article class="group flex flex-col items-center gap-3">
      <div class="relative w-full aspect-square overflow-visible">
        <div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih11-moon/600/600'),radial-gradient(circle,oklch(0.28_0.11_290),oklch(0.35_0.18_295),oklch(0.55_0.24_295))] bg-cover bg-center bg-no-repeat [clip-path:circle(42%_at_50%_50%)] transition-[clip-path,scale] duration-500 ease-tih11 group-hover:[clip-path:polygon(10%_0%,90%_0%,100%_10%,100%_90%,90%_100%,10%_100%,0%_90%,0%_10%)] group-hover:scale-105 motion-reduce:transition-none motion-reduce:group-hover:[clip-path:circle(42%_at_50%_50%)] motion-reduce:group-hover:scale-100">
        </div>
      </div>
      <div class="text-center">
        <p class="text-[10px] tracking-[0.08em] text-tih11-muted">Circle<span class="mx-1 text-tih11-acc">&rarr;</span><span class="font-bold text-tih11-acc">Rounded Rect</span></p>
        <p class="mt-0.75 text-[13px] font-bold text-tih11-text">Orbital Shift</p>
      </div>
    </article>
    <article class="group flex flex-col items-center gap-3">
      <div class="relative w-full aspect-square overflow-visible">
        <div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih11-gem/600/600'),radial-gradient(circle,oklch(0.25_0.05_180),oklch(0.42_0.09_165),oklch(0.8_0.14_163))] bg-cover bg-center bg-no-repeat [clip-path:polygon(50%_0%,100%_50%,50%_100%,0%_50%)] transition-[clip-path,scale] duration-500 ease-tih11 group-hover:[clip-path:circle(48%_at_50%_50%)] group-hover:scale-105 motion-reduce:transition-none motion-reduce:group-hover:[clip-path:polygon(50%_0%,100%_50%,50%_100%,0%_50%)] motion-reduce:group-hover:scale-100">
        </div>
      </div>
      <div class="text-center">
        <p class="text-[10px] tracking-[0.08em] text-tih11-muted">Diamond<span class="mx-1 text-tih11-acc">&rarr;</span><span class="font-bold text-tih11-acc">Circle</span></p>
        <p class="mt-0.75 text-[13px] font-bold text-tih11-text">Gem Reveal</p>
      </div>
    </article>
    <article class="group flex flex-col items-center gap-3">
      <div class="relative w-full aspect-square overflow-visible">
        <div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih11-lattice/600/600'),radial-gradient(circle,oklch(0.2_0.02_60),oklch(0.45_0.11_60),oklch(0.78_0.16_72))] bg-cover bg-center bg-no-repeat [clip-path:polygon(50%_0%,100%_25%,100%_75%,50%_100%,0%_75%,0%_25%)] transition-[clip-path,scale,rotate] duration-500 ease-tih11 group-hover:[clip-path:polygon(25%_0%,75%_0%,100%_50%,75%_100%,25%_100%,0%_50%)] group-hover:scale-105 group-hover:rotate-30 motion-reduce:transition-none motion-reduce:group-hover:[clip-path:polygon(50%_0%,100%_25%,100%_75%,50%_100%,0%_75%,0%_25%)] motion-reduce:group-hover:scale-100 motion-reduce:group-hover:rotate-0">
        </div>
      </div>
      <div class="text-center">
        <p class="text-[10px] tracking-[0.08em] text-tih11-muted">Hex<span class="mx-1 text-tih11-acc">&rarr;</span><span class="font-bold text-tih11-acc">Wide Hex + Spin</span></p>
        <p class="mt-0.75 text-[13px] font-bold text-tih11-text">Lattice Morph</p>
      </div>
    </article>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tih11-bg: #08080d;
  --color-tih11-bg: oklch(0.12 0.008 285);
  --color-tih11-text: #f1f5f9;
  --color-tih11-text: oklch(0.96 0.005 250);
  --color-tih11-muted: #64748b;
  --color-tih11-muted: oklch(0.58 0.035 255);
  --color-tih11-acc: #e879f9;
  --color-tih11-acc: oklch(0.78 0.19 320);
  --font-tih11-sans: 'Space Grotesk', system-ui, sans-serif;
  --ease-tih11: cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
   w-full min-h-svh, so the stage is correctly sized before the browser build compiles. */

.tih-11 {
  width: 100%;
  min-height: 100svh;
}

.tih-11 ::selection {
  background: oklch(0.78 0.19 320);
  color: #000;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tih11-bg: #08080d;
  --color-tih11-bg: oklch(0.12 0.008 285);
  --color-tih11-text: #f1f5f9;
  --color-tih11-text: oklch(0.96 0.005 250);
  --color-tih11-muted: #64748b;
  --color-tih11-muted: oklch(0.58 0.035 255);
  --color-tih11-acc: #e879f9;
  --color-tih11-acc: oklch(0.78 0.19 320);
  --font-tih11-sans: 'Space Grotesk', system-ui, sans-serif;
  --ease-tih11: cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
   w-full min-h-svh, so the stage is correctly sized before the browser build compiles. */

.tih-11 {
  width: 100%;
  min-height: 100svh;
}

.tih-11 ::selection {
  background: oklch(0.78 0.19 320);
  color: #000;
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • Six rules (three resting shapes, three hover shapes) become three elements carrying both of their own shapes. For a pattern whose main failure is mismatched vertex counts, adjacency is the feature.
  • transform: scale(1.05) rotate(30deg) becomes group-hover:scale-105 group-hover:rotate-30 — two individual properties, independently overridable, which the shorthand cannot do.
  • transition-[clip-path,scale,rotate] names the individual properties rather than transform, because that is what v4's utilities actually set.
  • The spring easing stays a single @theme entry shared by all three cards, so the overshoot cannot drift between them.
  • motion-reduce:group-hover:[clip-path:…] repeats the resting shape to cancel the morph. Verbose, and still better than a media-query block restating three selectors.
  • Nothing but the root rule and ::selection remains in CSS — even the radial-gradient fills became utilities.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Image Clip-Path Transition Hover
Source: https://codefronts.com/motion/tailwind-css-image-hover-effects/tailwind-image-clip-path-transition-hover/

Shape morphing where both shapes sit on the same element as two arbitrary clip-path utilities &mdash; resting and hovered, one line apart. Since interpolation requires matching vertex counts, having both values visible together is not cosmetic: it is what stops the pair from silently degrading into a hard cut.
## HTML
```html
<section class="tih-11 w-full min-h-svh grid place-items-center bg-tih11-bg font-tih11-sans px-6 py-10" aria-label="Clip-path shape morph hover demo">
  <div class="grid w-full max-w-195 gap-5 grid-cols-1 sm:grid-cols-3">
    <article class="group flex flex-col items-center gap-3">
      <div class="relative w-full aspect-square overflow-visible">
        <div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih11-moon/600/600'),radial-gradient(circle,oklch(0.28_0.11_290),oklch(0.35_0.18_295),oklch(0.55_0.24_295))] bg-cover bg-center bg-no-repeat [clip-path:circle(42%_at_50%_50%)] transition-[clip-path,scale] duration-500 ease-tih11 group-hover:[clip-path:polygon(10%_0%,90%_0%,100%_10%,100%_90%,90%_100%,10%_100%,0%_90%,0%_10%)] group-hover:scale-105 motion-reduce:transition-none motion-reduce:group-hover:[clip-path:circle(42%_at_50%_50%)] motion-reduce:group-hover:scale-100">
        </div>
      </div>
      <div class="text-center">
        <p class="text-[10px] tracking-[0.08em] text-tih11-muted">Circle<span class="mx-1 text-tih11-acc">&rarr;</span><span class="font-bold text-tih11-acc">Rounded Rect</span></p>
        <p class="mt-0.75 text-[13px] font-bold text-tih11-text">Orbital Shift</p>
      </div>
    </article>
    <article class="group flex flex-col items-center gap-3">
      <div class="relative w-full aspect-square overflow-visible">
        <div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih11-gem/600/600'),radial-gradient(circle,oklch(0.25_0.05_180),oklch(0.42_0.09_165),oklch(0.8_0.14_163))] bg-cover bg-center bg-no-repeat [clip-path:polygon(50%_0%,100%_50%,50%_100%,0%_50%)] transition-[clip-path,scale] duration-500 ease-tih11 group-hover:[clip-path:circle(48%_at_50%_50%)] group-hover:scale-105 motion-reduce:transition-none motion-reduce:group-hover:[clip-path:polygon(50%_0%,100%_50%,50%_100%,0%_50%)] motion-reduce:group-hover:scale-100">
        </div>
      </div>
      <div class="text-center">
        <p class="text-[10px] tracking-[0.08em] text-tih11-muted">Diamond<span class="mx-1 text-tih11-acc">&rarr;</span><span class="font-bold text-tih11-acc">Circle</span></p>
        <p class="mt-0.75 text-[13px] font-bold text-tih11-text">Gem Reveal</p>
      </div>
    </article>
    <article class="group flex flex-col items-center gap-3">
      <div class="relative w-full aspect-square overflow-visible">
        <div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih11-lattice/600/600'),radial-gradient(circle,oklch(0.2_0.02_60),oklch(0.45_0.11_60),oklch(0.78_0.16_72))] bg-cover bg-center bg-no-repeat [clip-path:polygon(50%_0%,100%_25%,100%_75%,50%_100%,0%_75%,0%_25%)] transition-[clip-path,scale,rotate] duration-500 ease-tih11 group-hover:[clip-path:polygon(25%_0%,75%_0%,100%_50%,75%_100%,25%_100%,0%_50%)] group-hover:scale-105 group-hover:rotate-30 motion-reduce:transition-none motion-reduce:group-hover:[clip-path:polygon(50%_0%,100%_25%,100%_75%,50%_100%,0%_75%,0%_25%)] motion-reduce:group-hover:scale-100 motion-reduce:group-hover:rotate-0">
        </div>
      </div>
      <div class="text-center">
        <p class="text-[10px] tracking-[0.08em] text-tih11-muted">Hex<span class="mx-1 text-tih11-acc">&rarr;</span><span class="font-bold text-tih11-acc">Wide Hex + Spin</span></p>
        <p class="mt-0.75 text-[13px] font-bold text-tih11-text">Lattice Morph</p>
      </div>
    </article>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tih11-bg: #08080d;
  --color-tih11-bg: oklch(0.12 0.008 285);
  --color-tih11-text: #f1f5f9;
  --color-tih11-text: oklch(0.96 0.005 250);
  --color-tih11-muted: #64748b;
  --color-tih11-muted: oklch(0.58 0.035 255);
  --color-tih11-acc: #e879f9;
  --color-tih11-acc: oklch(0.78 0.19 320);
  --font-tih11-sans: 'Space Grotesk', system-ui, sans-serif;
  --ease-tih11: cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
   w-full min-h-svh, so the stage is correctly sized before the browser build compiles. */

.tih-11 {
  width: 100%;
  min-height: 100svh;
}

.tih-11 ::selection {
  background: oklch(0.78 0.19 320);
  color: #000;
}
```

How this works

Both shapes, one element.

<div class="[clip-path:circle(42%_at_50%_50%)]
   transition-[clip-path,scale] duration-500 ease-tih11
   group-hover:[clip-path:polygon(10%_0%,90%_0%,100%_10%,100%_90%,90%_100%,10%_100%,0%_90%,0%_10%)]
   group-hover:scale-105">

The circle is a special case that interpolates to anything; the polygon pairs must match vertex for vertex. Writing them adjacently makes a mismatch a visible diff rather than a runtime mystery.

Why an octagon. Going circle → rectangle cleanly means expressing the rectangle as an eight-point polygon with clipped corners, which is also a better-looking target than a hard square.

Composed, not concatenated. The hexagon's hover state is scale-105 rotate-30 — two utilities compiling to two individual properties, where the CSS version writes transform: scale(1.05) rotate(30deg) and must restate the scale to change the rotation.

Make it yours

  • Any shape pair works if the vertex counts match; the customization the CSS version suggests (a ten-point star) is a single utility swap here.
  • Spring tension: ease-[cubic-bezier(0.34,2,0.64,1)] for more overshoot.
  • Speed: duration-500 → duration-300 reads as mechanical, duration-700 as liquid.
  • Add group-hover:rotate-15 to any card — rotation composes with the morph without touching the clip values.
  • Swap the three photographs in the bg-[image:…] utilities; each shape's fill is local to its markup, and the radial gradient behind it stays as the fallback.

Gotchas — read before shipping

  • Matching vertex counts is the hard requirement. A four-point polygon morphing to a six-point polygon hard-cuts, and nothing warns you.
  • clip-path clips box-shadow too — a glow on a clipped element is invisible outside the path.
  • Arbitrary clip-path values must use underscores for every space, and percentages must be typed closed-up: polygon(10%_0%,90%_0%,…).
  • Keep the frame overflow-visible: the shapes morph outside the inscribed circle, and a clip on the frame would cut the corners off mid-transition.
  • The compiler needs literal class strings. A polygon assembled from JS produces no CSS — put varying values in a custom property instead.
  • The browser CDN build compiles in the page and is for prototyping only. Ship the CLI or a plugin build.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Animatable basic-shape clip-path is Baseline; Safari below 9.1 needs -webkit-clip-path, which Tailwind does not emit for arbitrary properties.

Techniques used in this demo

Arbitraryneeds bracket syntax

Search CodeFronts

Loading…