04 / 36
All 36 in 36 Tailwind CSS Button Hover Effects
- 01Tailwind Magnetic Ripple Attraction Button Hover Effect
- 02Tailwind Cyberpunk Holographic Glitch Grid Button
- 03Tailwind Frosted Glassmorphism Shimmer Glow Button
- 04Tailwind Organic Blob Morph Button Hover Effect
- 05Tailwind Dual-Line Intersection Border Draw Button
- 06Tailwind Isometric Tactile 3D Depress Button
- 07Tailwind Infinite Sliding Kinetic Marquee Button
- 08Tailwind Chroma Conic Gradient Border Spin Button
- 09Tailwind Minimalist Border & Outline Drawing Hover Effects
- 10Tailwind Modern 3D Skeuomorphic Button Press Effects
- 11Tailwind Neo-Brutalist & Cyberpunk Button Glow Effects
- 12Tailwind Sliding Background Color Wipe Button Effects
- 13Tailwind Liquid Fill & Organic Morphing Button Effects
- 14Tailwind Kinetic Text Flip & Icon Reveal Button Effects
- 15Tailwind Liquid Wave Fill Button Hover Effect
- 16Tailwind Magnetic Border Drift Button
- 17Tailwind Glitch Slice Button Hover Effect
- 18Tailwind Neon Pulse Glow Button Hover Effect
- 19Tailwind Split Reveal Curtain Button Effect
- 20Tailwind Diagonal Shutter Sweep Button Effect
- 21Tailwind Elastic Lift Spring Button Hover Effect
- 22Tailwind Center Underline Draw Link Button Effects
- 23Tailwind 3D Flip Card Button Hover Effect
- 24Tailwind Particle Burst Confetti Button Effect
- 25Tailwind Slide Doors Closing Panel Button Effect
- 26Tailwind Typewriter Retype Terminal Button Effect
- 27Tailwind Hard Shadow Shift Flat Button Effect
- 28Tailwind Material Ripple Wave Button Effect
- 29Tailwind Rainbow Gradient Border Spin Button
- 30Tailwind Morph Shape Silhouette Button Effects
- 31Tailwind Icon Slide In Directional Button Effect
- 32Tailwind Cursor Spotlight Glow Button
- 33Tailwind Stagger Fill Equaliser Button Effect
- 34Tailwind Sunburst Expand Radiant Button Effect
- 35Tailwind 3D Press Plunger Key Button Effect
- 36Tailwind Add-to-Cart Liquid Glass Ripple Button
Tailwind Organic Blob Morph Button Hover Effect
A playful CTA that starts as a soft organic blob and, on hover, resolves into a clean structured pill — great for creative studios and friendly product pages. The port's interesting constraint: an eight-value border-radius is not a design-scale value, so it stays an arbitrary property, and that is the right answer.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tbh-04-group w-full min-h-svh flex flex-wrap">
<section class="tbh-04a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh04-sans bg-[radial-gradient(120%_120%_at_30%_0%,var(--color-tbh04a-lift),var(--color-tbh04a-bg))]" aria-label="Blob morph button">
<button type="button" class="grid place-items-center min-w-[180px] cursor-pointer border-0 px-10 py-6 text-[17px] font-extrabold text-white bg-[linear-gradient(150deg,color-mix(in_oklab,var(--color-tbh04a-acc)_82%,white),var(--color-tbh04a-acc))] shadow-[0_18px_36px_-16px_color-mix(in_oklab,var(--color-tbh04a-acc)_70%,transparent)] [border-radius:42%_58%_63%_37%_/_45%_38%_62%_55%] animate-tbh04a-breathe transition-[border-radius,translate,scale] duration-[600ms] ease-[cubic-bezier(.2,.9,.2,1)] hover:rounded-full hover:-translate-y-[3px] hover:[animation-play-state:paused] focus-visible:rounded-full focus-visible:-translate-y-[3px] focus-visible:[animation-play-state:paused] active:translate-y-0 active:scale-[.97] focus-visible:outline-3 focus-visible:outline-tbh04a-acc focus-visible:outline-offset-4 motion-reduce:animate-none motion-reduce:transition-[border-radius] motion-reduce:hover:translate-y-0">
Say hello
</button>
</section>
<section class="tbh-04b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh04-sans bg-tbh04b-bg" aria-label="Squircle morph button">
<button type="button" aria-label="Play" class="grid place-items-center w-24 h-24 cursor-pointer border-0 text-[26px] text-white bg-[linear-gradient(150deg,color-mix(in_oklab,var(--color-tbh04b-acc)_80%,white),var(--color-tbh04b-acc))] shadow-[0_18px_40px_-16px_var(--color-tbh04b-acc)] [border-radius:34%_66%_62%_38%_/_40%_42%_58%_60%] transition-[border-radius,scale] duration-[550ms] ease-[cubic-bezier(.2,.9,.2,1)] hover:rounded-[50%] hover:scale-[1.08] focus-visible:rounded-[50%] focus-visible:scale-[1.08] active:scale-[.96] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-[border-radius] motion-reduce:hover:scale-100">
<span aria-hidden="true">▶</span>
</button>
</section>
</div><div class="tbh-04-group w-full min-h-svh flex flex-wrap">
<section class="tbh-04a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh04-sans bg-[radial-gradient(120%_120%_at_30%_0%,var(--color-tbh04a-lift),var(--color-tbh04a-bg))]" aria-label="Blob morph button">
<button type="button" class="grid place-items-center min-w-[180px] cursor-pointer border-0 px-10 py-6 text-[17px] font-extrabold text-white bg-[linear-gradient(150deg,color-mix(in_oklab,var(--color-tbh04a-acc)_82%,white),var(--color-tbh04a-acc))] shadow-[0_18px_36px_-16px_color-mix(in_oklab,var(--color-tbh04a-acc)_70%,transparent)] [border-radius:42%_58%_63%_37%_/_45%_38%_62%_55%] animate-tbh04a-breathe transition-[border-radius,translate,scale] duration-[600ms] ease-[cubic-bezier(.2,.9,.2,1)] hover:rounded-full hover:-translate-y-[3px] hover:[animation-play-state:paused] focus-visible:rounded-full focus-visible:-translate-y-[3px] focus-visible:[animation-play-state:paused] active:translate-y-0 active:scale-[.97] focus-visible:outline-3 focus-visible:outline-tbh04a-acc focus-visible:outline-offset-4 motion-reduce:animate-none motion-reduce:transition-[border-radius] motion-reduce:hover:translate-y-0">
Say hello
</button>
</section>
<section class="tbh-04b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh04-sans bg-tbh04b-bg" aria-label="Squircle morph button">
<button type="button" aria-label="Play" class="grid place-items-center w-24 h-24 cursor-pointer border-0 text-[26px] text-white bg-[linear-gradient(150deg,color-mix(in_oklab,var(--color-tbh04b-acc)_80%,white),var(--color-tbh04b-acc))] shadow-[0_18px_40px_-16px_var(--color-tbh04b-acc)] [border-radius:34%_66%_62%_38%_/_40%_42%_58%_60%] transition-[border-radius,scale] duration-[550ms] ease-[cubic-bezier(.2,.9,.2,1)] hover:rounded-[50%] hover:scale-[1.08] focus-visible:rounded-[50%] focus-visible:scale-[1.08] active:scale-[.96] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-[border-radius] motion-reduce:hover:scale-100">
<span aria-hidden="true">▶</span>
</button>
</section>
</div>@import "tailwindcss";
@theme {
--color-tbh04a-acc: #f2734a;
--color-tbh04a-acc: oklch(0.68 0.17 25);
--color-tbh04a-bg: #ffe1d3;
--color-tbh04a-bg: oklch(0.92 0.04 45);
--color-tbh04a-lift: #fff3ec;
--color-tbh04a-lift: oklch(0.97 0.015 45);
--color-tbh04b-acc: #9a5cf0;
--color-tbh04b-acc: oklch(0.62 0.18 300);
--color-tbh04b-bg: #141020;
--color-tbh04b-bg: oklch(0.18 0.03 290);
--font-tbh04-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh04a-breathe: tbh04a-breathe 7s ease-in-out infinite;
}
/* Stays CSS: a keyframe interpolating two eight-value border-radius sets. Both ends must use
the identical syntax form, or the browser refuses to interpolate and snaps instead. */
@keyframes tbh04a-breathe {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-04-group {
width: 100%;
min-height: 100svh;
}
.tbh-04a,
.tbh-04b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh04a-acc: #f2734a;
--color-tbh04a-acc: oklch(0.68 0.17 25);
--color-tbh04a-bg: #ffe1d3;
--color-tbh04a-bg: oklch(0.92 0.04 45);
--color-tbh04a-lift: #fff3ec;
--color-tbh04a-lift: oklch(0.97 0.015 45);
--color-tbh04b-acc: #9a5cf0;
--color-tbh04b-acc: oklch(0.62 0.18 300);
--color-tbh04b-bg: #141020;
--color-tbh04b-bg: oklch(0.18 0.03 290);
--font-tbh04-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh04a-breathe: tbh04a-breathe 7s ease-in-out infinite;
}
/* Stays CSS: a keyframe interpolating two eight-value border-radius sets. Both ends must use
the identical syntax form, or the browser refuses to interpolate and snaps instead. */
@keyframes tbh04a-breathe {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-04-group {
width: 100%;
min-height: 100svh;
}
.tbh-04a,
.tbh-04b {
min-height: 100svh;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<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.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tbh04a-acc: #f2734a;
--color-tbh04a-acc: oklch(0.68 0.17 25);
--color-tbh04a-bg: #ffe1d3;
--color-tbh04a-bg: oklch(0.92 0.04 45);
--color-tbh04a-lift: #fff3ec;
--color-tbh04a-lift: oklch(0.97 0.015 45);
--color-tbh04b-acc: #9a5cf0;
--color-tbh04b-acc: oklch(0.62 0.18 300);
--color-tbh04b-bg: #141020;
--color-tbh04b-bg: oklch(0.18 0.03 290);
--font-tbh04-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh04a-breathe: tbh04a-breathe 7s ease-in-out infinite;
}
/* Stays CSS: a keyframe interpolating two eight-value border-radius sets. Both ends must use
the identical syntax form, or the browser refuses to interpolate and snaps instead. */
@keyframes tbh04a-breathe {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-04-group {
width: 100%;
min-height: 100svh;
}
.tbh-04a,
.tbh-04b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh04a-acc: #f2734a;
--color-tbh04a-acc: oklch(0.68 0.17 25);
--color-tbh04a-bg: #ffe1d3;
--color-tbh04a-bg: oklch(0.92 0.04 45);
--color-tbh04a-lift: #fff3ec;
--color-tbh04a-lift: oklch(0.97 0.015 45);
--color-tbh04b-acc: #9a5cf0;
--color-tbh04b-acc: oklch(0.62 0.18 300);
--color-tbh04b-bg: #141020;
--color-tbh04b-bg: oklch(0.18 0.03 290);
--font-tbh04-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh04a-breathe: tbh04a-breathe 7s ease-in-out infinite;
}
/* Stays CSS: a keyframe interpolating two eight-value border-radius sets. Both ends must use
the identical syntax form, or the browser refuses to interpolate and snaps instead. */
@keyframes tbh04a-breathe {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-04-group {
width: 100%;
min-height: 100svh;
}
.tbh-04a,
.tbh-04b {
min-height: 100svh;
}<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&display=swap">
<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&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The one demo in the batch where the central value resists utilities entirely. An eight-value
border-radiusis a one-off silhouette, not a scale step, so[border-radius:...]is the idiomatic answer rather than a workaround — and the_/_escape is the only syntax subtlety in it. animation-play-stateis the other CSS-shaped hole: no utility, no variant shorthand, sohover:[animation-play-state:paused]. Worth knowing before you reach for a JS class toggle to do the same thing.- UI change from the CSS original: both designs now mirror every hover state on
focus-visible:(the original morphed on focus but not consistently), and the press states are split intoactive:translate-y-0 active:scale-[.97]so the lift and the squash no longer overwrite each other.
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 Organic Blob Morph Button Hover Effect
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-organic-blob-morph-button-hover-effect/
A playful CTA that starts as a soft organic blob and, on hover, resolves into a clean structured pill — great for creative studios and friendly product pages. The port's interesting constraint: an eight-value border-radius is not a design-scale value, so it stays an arbitrary property, and that is the right answer.
## HTML
```html
<div class="tbh-04-group w-full min-h-svh flex flex-wrap">
<section class="tbh-04a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh04-sans bg-[radial-gradient(120%_120%_at_30%_0%,var(--color-tbh04a-lift),var(--color-tbh04a-bg))]" aria-label="Blob morph button">
<button type="button" class="grid place-items-center min-w-[180px] cursor-pointer border-0 px-10 py-6 text-[17px] font-extrabold text-white bg-[linear-gradient(150deg,color-mix(in_oklab,var(--color-tbh04a-acc)_82%,white),var(--color-tbh04a-acc))] shadow-[0_18px_36px_-16px_color-mix(in_oklab,var(--color-tbh04a-acc)_70%,transparent)] [border-radius:42%_58%_63%_37%_/_45%_38%_62%_55%] animate-tbh04a-breathe transition-[border-radius,translate,scale] duration-[600ms] ease-[cubic-bezier(.2,.9,.2,1)] hover:rounded-full hover:-translate-y-[3px] hover:[animation-play-state:paused] focus-visible:rounded-full focus-visible:-translate-y-[3px] focus-visible:[animation-play-state:paused] active:translate-y-0 active:scale-[.97] focus-visible:outline-3 focus-visible:outline-tbh04a-acc focus-visible:outline-offset-4 motion-reduce:animate-none motion-reduce:transition-[border-radius] motion-reduce:hover:translate-y-0">
Say hello
</button>
</section>
<section class="tbh-04b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh04-sans bg-tbh04b-bg" aria-label="Squircle morph button">
<button type="button" aria-label="Play" class="grid place-items-center w-24 h-24 cursor-pointer border-0 text-[26px] text-white bg-[linear-gradient(150deg,color-mix(in_oklab,var(--color-tbh04b-acc)_80%,white),var(--color-tbh04b-acc))] shadow-[0_18px_40px_-16px_var(--color-tbh04b-acc)] [border-radius:34%_66%_62%_38%_/_40%_42%_58%_60%] transition-[border-radius,scale] duration-[550ms] ease-[cubic-bezier(.2,.9,.2,1)] hover:rounded-[50%] hover:scale-[1.08] focus-visible:rounded-[50%] focus-visible:scale-[1.08] active:scale-[.96] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-[border-radius] motion-reduce:hover:scale-100">
<span aria-hidden="true">▶</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh04a-acc: #f2734a;
--color-tbh04a-acc: oklch(0.68 0.17 25);
--color-tbh04a-bg: #ffe1d3;
--color-tbh04a-bg: oklch(0.92 0.04 45);
--color-tbh04a-lift: #fff3ec;
--color-tbh04a-lift: oklch(0.97 0.015 45);
--color-tbh04b-acc: #9a5cf0;
--color-tbh04b-acc: oklch(0.62 0.18 300);
--color-tbh04b-bg: #141020;
--color-tbh04b-bg: oklch(0.18 0.03 290);
--font-tbh04-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh04a-breathe: tbh04a-breathe 7s ease-in-out infinite;
}
/* Stays CSS: a keyframe interpolating two eight-value border-radius sets. Both ends must use
the identical syntax form, or the browser refuses to interpolate and snaps instead. */
@keyframes tbh04a-breathe {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-04-group {
width: 100%;
min-height: 100svh;
}
.tbh-04a,
.tbh-04b {
min-height: 100svh;
}
```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 Organic Blob Morph Button Hover Effect
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-organic-blob-morph-button-hover-effect/
A playful CTA that starts as a soft organic blob and, on hover, resolves into a clean structured pill — great for creative studios and friendly product pages. The port's interesting constraint: an eight-value border-radius is not a design-scale value, so it stays an arbitrary property, and that is the right answer.
## HTML
```html
<div class="tbh-04-group w-full min-h-svh flex flex-wrap">
<section class="tbh-04a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh04-sans bg-[radial-gradient(120%_120%_at_30%_0%,var(--color-tbh04a-lift),var(--color-tbh04a-bg))]" aria-label="Blob morph button">
<button type="button" class="grid place-items-center min-w-[180px] cursor-pointer border-0 px-10 py-6 text-[17px] font-extrabold text-white bg-[linear-gradient(150deg,color-mix(in_oklab,var(--color-tbh04a-acc)_82%,white),var(--color-tbh04a-acc))] shadow-[0_18px_36px_-16px_color-mix(in_oklab,var(--color-tbh04a-acc)_70%,transparent)] [border-radius:42%_58%_63%_37%_/_45%_38%_62%_55%] animate-tbh04a-breathe transition-[border-radius,translate,scale] duration-[600ms] ease-[cubic-bezier(.2,.9,.2,1)] hover:rounded-full hover:-translate-y-[3px] hover:[animation-play-state:paused] focus-visible:rounded-full focus-visible:-translate-y-[3px] focus-visible:[animation-play-state:paused] active:translate-y-0 active:scale-[.97] focus-visible:outline-3 focus-visible:outline-tbh04a-acc focus-visible:outline-offset-4 motion-reduce:animate-none motion-reduce:transition-[border-radius] motion-reduce:hover:translate-y-0">
Say hello
</button>
</section>
<section class="tbh-04b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh04-sans bg-tbh04b-bg" aria-label="Squircle morph button">
<button type="button" aria-label="Play" class="grid place-items-center w-24 h-24 cursor-pointer border-0 text-[26px] text-white bg-[linear-gradient(150deg,color-mix(in_oklab,var(--color-tbh04b-acc)_80%,white),var(--color-tbh04b-acc))] shadow-[0_18px_40px_-16px_var(--color-tbh04b-acc)] [border-radius:34%_66%_62%_38%_/_40%_42%_58%_60%] transition-[border-radius,scale] duration-[550ms] ease-[cubic-bezier(.2,.9,.2,1)] hover:rounded-[50%] hover:scale-[1.08] focus-visible:rounded-[50%] focus-visible:scale-[1.08] active:scale-[.96] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-[border-radius] motion-reduce:hover:scale-100">
<span aria-hidden="true">▶</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh04a-acc: #f2734a;
--color-tbh04a-acc: oklch(0.68 0.17 25);
--color-tbh04a-bg: #ffe1d3;
--color-tbh04a-bg: oklch(0.92 0.04 45);
--color-tbh04a-lift: #fff3ec;
--color-tbh04a-lift: oklch(0.97 0.015 45);
--color-tbh04b-acc: #9a5cf0;
--color-tbh04b-acc: oklch(0.62 0.18 300);
--color-tbh04b-bg: #141020;
--color-tbh04b-bg: oklch(0.18 0.03 290);
--font-tbh04-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh04a-breathe: tbh04a-breathe 7s ease-in-out infinite;
}
/* Stays CSS: a keyframe interpolating two eight-value border-radius sets. Both ends must use
the identical syntax form, or the browser refuses to interpolate and snaps instead. */
@keyframes tbh04a-breathe {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-04-group {
width: 100%;
min-height: 100svh;
}
.tbh-04a,
.tbh-04b {
min-height: 100svh;
}
```How this works
Why the radius is not a utility. Tailwind's rounded-* scale exists so that corner radii stay consistent across a product. A hand-tuned eight-value blob is the opposite of that: a one-off shape belonging to one element. Writing it as [border-radius:...] states that honestly, and the underscore escape carries the multi-value syntax through intact.
Hover both resolves and freezes. The blob breathes at rest via a keyframe that transitions between two radius sets. On hover, hover:rounded-full transitions to a symmetric pill while hover:[animation-play-state:paused] stops the wobble fighting it — the CSS original needed the same two declarations, but they sat in two different rules.
Design B is the icon case. A 96px squircle blob that morphs to a true circle and scales up 8% — the same technique at a size where the shape, not the label, is the whole button. Its accessible name comes from aria-label, since the glyph is decorative.
Make it yours
- Blob personality: the eight radius stops. Keep both keyframe ends in the same syntax form or the animation will not interpolate at all.
- Idle calm: drop
animate-tbh04a-breatheand the shape simply sits still until hover. - Morph speed:
duration-[600ms]withease-[cubic-bezier(.2,.9,.2,1)]— long enough to read as organic, short enough to feel responsive. - Target shape:
hover:rounded-fullon A,hover:rounded-[50%]on B; arounded-2xltarget reads as 'app icon' instead.
Gotchas — read before shipping
- Multi-value
border-radiusinterpolates only between two same-syntax forms. A four-value resting shape and an eight-value hover target will snap rather than morph. - Do not animate
widthto reshape the button — that relayouts the stage on every frame. Let the radius do the work. - Centre the label with
grid place-items-center, not absolute offsets, so it stays put while the silhouette breathes. [animation-play-state:paused]has no utility and no variant shorthand; the arbitrary property is the only form, and it must be written on the same element that carriesanimate-*.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Multi-value border-radius transitions are universal; the optional SVG path d morph mentioned in the CSS original remains Chromium-only, which is why this port keeps the radius technique.