13 / 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 Liquid Fill & Organic Morphing Button Effects
A tactile CTA where a wobbling organic blob swells from the centre to flood the button while its edges settle from a ripple into a clean fill — the centre-out liquid morph, distinct from entry 15's bottom-up wave. Two techniques from earlier entries (blob radius, scaled fill) combine here, and the port keeps both legible.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tbh-13-group w-full min-h-svh flex flex-wrap">
<section class="tbh-13a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh13-sans bg-tbh13a-stage" aria-label="Liquid fill morph button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-full border-2 border-tbh13a-liquid px-11 py-[18px] text-base font-extrabold text-tbh13a-liquid bg-transparent transition-colors duration-[350ms] delay-100 hover:text-tbh13a-ink focus-visible:text-tbh13a-ink focus-visible:outline-3 focus-visible:outline-tbh13a-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[120%] aspect-square z-0 bg-tbh13a-liquid scale-0 [border-radius:42%_58%_63%_37%_/_45%_38%_62%_55%] animate-tbh13a-wobble transition-[scale,border-radius] duration-[550ms] ease-[cubic-bezier(.2,.9,.2,1)] group-hover/btn:scale-[1.15] group-hover/btn:rounded-full group-hover/btn:[animation-play-state:paused] group-focus-visible/btn:scale-[1.15] group-focus-visible/btn:rounded-full group-focus-visible/btn:[animation-play-state:paused] motion-reduce:animate-none motion-reduce:duration-200"></span>
<span class="relative z-[1]">Try for free</span>
</button>
</section>
<section class="tbh-13b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh13-sans bg-tbh13b-stage" aria-label="Gooey drip button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-[14px] border-0 px-[46px] py-[18px] text-base font-extrabold text-white bg-[color-mix(in_oklab,var(--color-tbh13b-liquid)_30%,var(--color-tbh13b-stage))] transition-colors duration-300 focus-visible:outline-3 focus-visible:outline-tbh13b-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute left-0 -top-full w-full h-full z-0 bg-tbh13b-liquid [border-radius:0_0_46%_54%_/_0_0_40%_40%] transition-[translate] duration-500 ease-[cubic-bezier(.5,0,.2,1)] group-hover/btn:translate-y-full group-focus-visible/btn:translate-y-full motion-reduce:duration-[250ms]"></span>
<span class="relative z-[1]">Splash in</span>
</button>
</section>
</div><div class="tbh-13-group w-full min-h-svh flex flex-wrap">
<section class="tbh-13a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh13-sans bg-tbh13a-stage" aria-label="Liquid fill morph button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-full border-2 border-tbh13a-liquid px-11 py-[18px] text-base font-extrabold text-tbh13a-liquid bg-transparent transition-colors duration-[350ms] delay-100 hover:text-tbh13a-ink focus-visible:text-tbh13a-ink focus-visible:outline-3 focus-visible:outline-tbh13a-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[120%] aspect-square z-0 bg-tbh13a-liquid scale-0 [border-radius:42%_58%_63%_37%_/_45%_38%_62%_55%] animate-tbh13a-wobble transition-[scale,border-radius] duration-[550ms] ease-[cubic-bezier(.2,.9,.2,1)] group-hover/btn:scale-[1.15] group-hover/btn:rounded-full group-hover/btn:[animation-play-state:paused] group-focus-visible/btn:scale-[1.15] group-focus-visible/btn:rounded-full group-focus-visible/btn:[animation-play-state:paused] motion-reduce:animate-none motion-reduce:duration-200"></span>
<span class="relative z-[1]">Try for free</span>
</button>
</section>
<section class="tbh-13b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh13-sans bg-tbh13b-stage" aria-label="Gooey drip button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-[14px] border-0 px-[46px] py-[18px] text-base font-extrabold text-white bg-[color-mix(in_oklab,var(--color-tbh13b-liquid)_30%,var(--color-tbh13b-stage))] transition-colors duration-300 focus-visible:outline-3 focus-visible:outline-tbh13b-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute left-0 -top-full w-full h-full z-0 bg-tbh13b-liquid [border-radius:0_0_46%_54%_/_0_0_40%_40%] transition-[translate] duration-500 ease-[cubic-bezier(.5,0,.2,1)] group-hover/btn:translate-y-full group-focus-visible/btn:translate-y-full motion-reduce:duration-[250ms]"></span>
<span class="relative z-[1]">Splash in</span>
</button>
</section>
</div>@import "tailwindcss";
@theme {
--color-tbh13a-liquid: #2bb3c9;
--color-tbh13a-liquid: oklch(0.6 0.19 195);
--color-tbh13a-ink: #04191d;
--color-tbh13a-ink: oklch(0.21 0.03 200);
--color-tbh13a-stage: #0e1a1f;
--color-tbh13a-stage: oklch(0.17 0.02 210);
--color-tbh13b-liquid: #e668b4;
--color-tbh13b-liquid: oklch(0.66 0.19 330);
--color-tbh13b-stage: #1a0c18;
--color-tbh13b-stage: oklch(0.18 0.03 330);
--font-tbh13-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh13a-wobble: tbh13a-wobble 6s ease-in-out infinite;
}
/* Stays CSS: the idle wobble. Both keyframe ends use the identical eight-value syntax, which
is the precondition for border-radius interpolating at all. */
@keyframes tbh13a-wobble {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-13-group {
width: 100%;
min-height: 100svh;
}
.tbh-13a,
.tbh-13b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh13a-liquid: #2bb3c9;
--color-tbh13a-liquid: oklch(0.6 0.19 195);
--color-tbh13a-ink: #04191d;
--color-tbh13a-ink: oklch(0.21 0.03 200);
--color-tbh13a-stage: #0e1a1f;
--color-tbh13a-stage: oklch(0.17 0.02 210);
--color-tbh13b-liquid: #e668b4;
--color-tbh13b-liquid: oklch(0.66 0.19 330);
--color-tbh13b-stage: #1a0c18;
--color-tbh13b-stage: oklch(0.18 0.03 330);
--font-tbh13-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh13a-wobble: tbh13a-wobble 6s ease-in-out infinite;
}
/* Stays CSS: the idle wobble. Both keyframe ends use the identical eight-value syntax, which
is the precondition for border-radius interpolating at all. */
@keyframes tbh13a-wobble {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-13-group {
width: 100%;
min-height: 100svh;
}
.tbh-13a,
.tbh-13b {
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-tbh13a-liquid: #2bb3c9;
--color-tbh13a-liquid: oklch(0.6 0.19 195);
--color-tbh13a-ink: #04191d;
--color-tbh13a-ink: oklch(0.21 0.03 200);
--color-tbh13a-stage: #0e1a1f;
--color-tbh13a-stage: oklch(0.17 0.02 210);
--color-tbh13b-liquid: #e668b4;
--color-tbh13b-liquid: oklch(0.66 0.19 330);
--color-tbh13b-stage: #1a0c18;
--color-tbh13b-stage: oklch(0.18 0.03 330);
--font-tbh13-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh13a-wobble: tbh13a-wobble 6s ease-in-out infinite;
}
/* Stays CSS: the idle wobble. Both keyframe ends use the identical eight-value syntax, which
is the precondition for border-radius interpolating at all. */
@keyframes tbh13a-wobble {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-13-group {
width: 100%;
min-height: 100svh;
}
.tbh-13a,
.tbh-13b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh13a-liquid: #2bb3c9;
--color-tbh13a-liquid: oklch(0.6 0.19 195);
--color-tbh13a-ink: #04191d;
--color-tbh13a-ink: oklch(0.21 0.03 200);
--color-tbh13a-stage: #0e1a1f;
--color-tbh13a-stage: oklch(0.17 0.02 210);
--color-tbh13b-liquid: #e668b4;
--color-tbh13b-liquid: oklch(0.66 0.19 330);
--color-tbh13b-stage: #1a0c18;
--color-tbh13b-stage: oklch(0.18 0.03 330);
--font-tbh13-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh13a-wobble: tbh13a-wobble 6s ease-in-out infinite;
}
/* Stays CSS: the idle wobble. Both keyframe ends use the identical eight-value syntax, which
is the precondition for border-radius interpolating at all. */
@keyframes tbh13a-wobble {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-13-group {
width: 100%;
min-height: 100svh;
}
.tbh-13a,
.tbh-13b {
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 centring-versus-scale separation pays off most here.
transform:translate(-50%,-50%) scale(0)→transform:translate(-50%,-50%) scale(1.15)becomes a permanent-translate-x-1/2 -translate-y-1/2plusgroup-hover/btn:scale-[1.15], and the centring can no longer be lost by editing the hover state. group-hover/btn:rounded-fulloverriding an arbitrary[border-radius:...]works because the utility is emitted later in the same layer — worth knowing, since the reverse order would need an!flag.- UI change from the CSS original: both designs mirror the flood on
group-focus-visible/btn:, and design A's reduced-motion path stops the wobble but keeps a 200ms flood, so the hover is still confirmed rather than silent.
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 Liquid Fill & Organic Morphing Button Effects
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-liquid-fill-organic-morphing-button-effects/
A tactile CTA where a wobbling organic blob swells from the centre to flood the button while its edges settle from a ripple into a clean fill — the centre-out liquid morph, distinct from entry 15's bottom-up wave. Two techniques from earlier entries (blob radius, scaled fill) combine here, and the port keeps both legible.
## HTML
```html
<div class="tbh-13-group w-full min-h-svh flex flex-wrap">
<section class="tbh-13a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh13-sans bg-tbh13a-stage" aria-label="Liquid fill morph button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-full border-2 border-tbh13a-liquid px-11 py-[18px] text-base font-extrabold text-tbh13a-liquid bg-transparent transition-colors duration-[350ms] delay-100 hover:text-tbh13a-ink focus-visible:text-tbh13a-ink focus-visible:outline-3 focus-visible:outline-tbh13a-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[120%] aspect-square z-0 bg-tbh13a-liquid scale-0 [border-radius:42%_58%_63%_37%_/_45%_38%_62%_55%] animate-tbh13a-wobble transition-[scale,border-radius] duration-[550ms] ease-[cubic-bezier(.2,.9,.2,1)] group-hover/btn:scale-[1.15] group-hover/btn:rounded-full group-hover/btn:[animation-play-state:paused] group-focus-visible/btn:scale-[1.15] group-focus-visible/btn:rounded-full group-focus-visible/btn:[animation-play-state:paused] motion-reduce:animate-none motion-reduce:duration-200"></span>
<span class="relative z-[1]">Try for free</span>
</button>
</section>
<section class="tbh-13b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh13-sans bg-tbh13b-stage" aria-label="Gooey drip button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-[14px] border-0 px-[46px] py-[18px] text-base font-extrabold text-white bg-[color-mix(in_oklab,var(--color-tbh13b-liquid)_30%,var(--color-tbh13b-stage))] transition-colors duration-300 focus-visible:outline-3 focus-visible:outline-tbh13b-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute left-0 -top-full w-full h-full z-0 bg-tbh13b-liquid [border-radius:0_0_46%_54%_/_0_0_40%_40%] transition-[translate] duration-500 ease-[cubic-bezier(.5,0,.2,1)] group-hover/btn:translate-y-full group-focus-visible/btn:translate-y-full motion-reduce:duration-[250ms]"></span>
<span class="relative z-[1]">Splash in</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh13a-liquid: #2bb3c9;
--color-tbh13a-liquid: oklch(0.6 0.19 195);
--color-tbh13a-ink: #04191d;
--color-tbh13a-ink: oklch(0.21 0.03 200);
--color-tbh13a-stage: #0e1a1f;
--color-tbh13a-stage: oklch(0.17 0.02 210);
--color-tbh13b-liquid: #e668b4;
--color-tbh13b-liquid: oklch(0.66 0.19 330);
--color-tbh13b-stage: #1a0c18;
--color-tbh13b-stage: oklch(0.18 0.03 330);
--font-tbh13-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh13a-wobble: tbh13a-wobble 6s ease-in-out infinite;
}
/* Stays CSS: the idle wobble. Both keyframe ends use the identical eight-value syntax, which
is the precondition for border-radius interpolating at all. */
@keyframes tbh13a-wobble {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-13-group {
width: 100%;
min-height: 100svh;
}
.tbh-13a,
.tbh-13b {
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 Liquid Fill & Organic Morphing Button Effects
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-liquid-fill-organic-morphing-button-effects/
A tactile CTA where a wobbling organic blob swells from the centre to flood the button while its edges settle from a ripple into a clean fill — the centre-out liquid morph, distinct from entry 15's bottom-up wave. Two techniques from earlier entries (blob radius, scaled fill) combine here, and the port keeps both legible.
## HTML
```html
<div class="tbh-13-group w-full min-h-svh flex flex-wrap">
<section class="tbh-13a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh13-sans bg-tbh13a-stage" aria-label="Liquid fill morph button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-full border-2 border-tbh13a-liquid px-11 py-[18px] text-base font-extrabold text-tbh13a-liquid bg-transparent transition-colors duration-[350ms] delay-100 hover:text-tbh13a-ink focus-visible:text-tbh13a-ink focus-visible:outline-3 focus-visible:outline-tbh13a-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[120%] aspect-square z-0 bg-tbh13a-liquid scale-0 [border-radius:42%_58%_63%_37%_/_45%_38%_62%_55%] animate-tbh13a-wobble transition-[scale,border-radius] duration-[550ms] ease-[cubic-bezier(.2,.9,.2,1)] group-hover/btn:scale-[1.15] group-hover/btn:rounded-full group-hover/btn:[animation-play-state:paused] group-focus-visible/btn:scale-[1.15] group-focus-visible/btn:rounded-full group-focus-visible/btn:[animation-play-state:paused] motion-reduce:animate-none motion-reduce:duration-200"></span>
<span class="relative z-[1]">Try for free</span>
</button>
</section>
<section class="tbh-13b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh13-sans bg-tbh13b-stage" aria-label="Gooey drip button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-[14px] border-0 px-[46px] py-[18px] text-base font-extrabold text-white bg-[color-mix(in_oklab,var(--color-tbh13b-liquid)_30%,var(--color-tbh13b-stage))] transition-colors duration-300 focus-visible:outline-3 focus-visible:outline-tbh13b-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute left-0 -top-full w-full h-full z-0 bg-tbh13b-liquid [border-radius:0_0_46%_54%_/_0_0_40%_40%] transition-[translate] duration-500 ease-[cubic-bezier(.5,0,.2,1)] group-hover/btn:translate-y-full group-focus-visible/btn:translate-y-full motion-reduce:duration-[250ms]"></span>
<span class="relative z-[1]">Splash in</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh13a-liquid: #2bb3c9;
--color-tbh13a-liquid: oklch(0.6 0.19 195);
--color-tbh13a-ink: #04191d;
--color-tbh13a-ink: oklch(0.21 0.03 200);
--color-tbh13a-stage: #0e1a1f;
--color-tbh13a-stage: oklch(0.17 0.02 210);
--color-tbh13b-liquid: #e668b4;
--color-tbh13b-liquid: oklch(0.66 0.19 330);
--color-tbh13b-stage: #1a0c18;
--color-tbh13b-stage: oklch(0.18 0.03 330);
--font-tbh13-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh13a-wobble: tbh13a-wobble 6s ease-in-out infinite;
}
/* Stays CSS: the idle wobble. Both keyframe ends use the identical eight-value syntax, which
is the precondition for border-radius interpolating at all. */
@keyframes tbh13a-wobble {
0%,
100% {
border-radius: 42% 58% 63% 37% / 45% 38% 62% 55%;
}
50% {
border-radius: 60% 40% 38% 62% / 55% 62% 38% 45%;
}
}
.tbh-13-group {
width: 100%;
min-height: 100svh;
}
.tbh-13a,
.tbh-13b {
min-height: 100svh;
}
```How this works
Centring stays on the translate property, so scale is free. The CSS original wrote transform:translate(-50%,-50%) scale(0) and had to repeat the centring in the hover state. Here -translate-x-1/2 -translate-y-1/2 holds permanently and hover touches only scale — the single most repeated win in this collection, and this is its cleanest instance.
Two morphs at once. The blob grows AND its radius resolves to a pill, on slightly different durations (550ms scale, 500ms radius). That mismatch is deliberate: the shape settles just before the fill finishes, which is what makes it read as liquid rather than as a growing circle.
Design B is gravity instead of pressure. One layer, one translate, and a lopsided bottom radius that reads as a drip meeting the surface. Cheaper than the blob, and the better choice when the button sits in a dense UI.
Make it yours
- Flood extent:
group-hover/btn:scale-[1.15]. Below 1.0 the corners never fill; much above 1.3 wastes paint. - Blob character: the eight radius stops, and the wobble keyframe's second set.
- Idle calm: drop
animate-tbh13a-wobbleand the blob simply waits. - Recolour through
--color-tbh13a-liquid/-13b-liquid; border, fill and ring all read it.
Gotchas — read before shipping
overflow-hiddenon the button is what clips the swelling blob — without it the blob paints over the page as a growing circle.- Keep the resting
scale-0, nothidden: a display change cannot transition, and an opacity fade loses the liquid read entirely. - The label must invert on hover (
group-hover/btn:text-...) and the inverted colour needs its own contrast check against the liquid, not against the stage. aspect-squarewithw-[120%]makes the blob taller than the button on purpose; a percentage height would resolve against the button and leave the corners dry.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Multi-value border-radius and transforms animate everywhere; no fallback needed beyond Tailwind v4's own floor.