15 / 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 Wave Fill Button Hover Effect
A CTA that fills like a glass of water: on hover a coloured liquid rises from the bottom with a scalloped wave crest that sways — the bottom-up fill, distinct from entry 13's centre-out blob. The crest is a repeating radial-gradient, which is the trick worth stealing from this one.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tbh-15-group w-full min-h-svh flex flex-wrap">
<section class="tbh-15a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh15-sans bg-tbh15a-stage" aria-label="Liquid wave fill button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-xl border-2 border-tbh15a-liquid px-11 py-[18px] text-base font-extrabold text-tbh15a-liquid bg-transparent transition-colors duration-[350ms] delay-150 hover:text-white focus-visible:text-white focus-visible:outline-3 focus-visible:outline-tbh15a-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute -left-1/2 top-full w-[200%] h-full z-[1] bg-tbh15a-liquid translate-y-[22px] transition-[translate] duration-[550ms] ease-[cubic-bezier(.4,0,.2,1)] before:content-[''] before:absolute before:left-0 before:bottom-full before:w-full before:h-[22px] before:bg-[radial-gradient(circle_at_25%_0,transparent_12px,var(--color-tbh15a-liquid)_13px)] before:bg-[length:44px_22px] before:bg-repeat-x group-hover/btn:-translate-y-full group-hover/btn:animate-tbh15a-sway group-focus-visible/btn:-translate-y-full group-focus-visible/btn:animate-tbh15a-sway motion-reduce:transition-none motion-reduce:group-hover/btn:animate-none motion-reduce:group-focus-visible/btn:animate-none"></span>
<span class="relative z-[2]">Download</span>
</button>
</section>
<section class="tbh-15b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh15-sans bg-tbh15b-stage" aria-label="Tilted flood button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-full border-2 border-tbh15b-liquid px-[46px] py-[18px] text-base font-extrabold text-tbh15b-liquid bg-transparent transition-colors duration-300 delay-[120ms] hover:text-tbh15b-ink focus-visible:text-tbh15b-ink focus-visible:outline-3 focus-visible:outline-tbh15b-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute -left-[10%] bottom-0 w-[120%] h-[180%] z-0 origin-top bg-tbh15b-liquid [rotate:6deg] translate-y-[120%] transition-[translate,rotate] duration-500 ease-[cubic-bezier(.5,0,.2,1)] group-hover/btn:[rotate:0deg] group-hover/btn:translate-y-0 group-focus-visible/btn:[rotate:0deg] group-focus-visible/btn:translate-y-0 motion-reduce:duration-[250ms]"></span>
<span class="relative z-[1]">Install now</span>
</button>
</section>
</div><div class="tbh-15-group w-full min-h-svh flex flex-wrap">
<section class="tbh-15a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh15-sans bg-tbh15a-stage" aria-label="Liquid wave fill button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-xl border-2 border-tbh15a-liquid px-11 py-[18px] text-base font-extrabold text-tbh15a-liquid bg-transparent transition-colors duration-[350ms] delay-150 hover:text-white focus-visible:text-white focus-visible:outline-3 focus-visible:outline-tbh15a-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute -left-1/2 top-full w-[200%] h-full z-[1] bg-tbh15a-liquid translate-y-[22px] transition-[translate] duration-[550ms] ease-[cubic-bezier(.4,0,.2,1)] before:content-[''] before:absolute before:left-0 before:bottom-full before:w-full before:h-[22px] before:bg-[radial-gradient(circle_at_25%_0,transparent_12px,var(--color-tbh15a-liquid)_13px)] before:bg-[length:44px_22px] before:bg-repeat-x group-hover/btn:-translate-y-full group-hover/btn:animate-tbh15a-sway group-focus-visible/btn:-translate-y-full group-focus-visible/btn:animate-tbh15a-sway motion-reduce:transition-none motion-reduce:group-hover/btn:animate-none motion-reduce:group-focus-visible/btn:animate-none"></span>
<span class="relative z-[2]">Download</span>
</button>
</section>
<section class="tbh-15b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh15-sans bg-tbh15b-stage" aria-label="Tilted flood button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-full border-2 border-tbh15b-liquid px-[46px] py-[18px] text-base font-extrabold text-tbh15b-liquid bg-transparent transition-colors duration-300 delay-[120ms] hover:text-tbh15b-ink focus-visible:text-tbh15b-ink focus-visible:outline-3 focus-visible:outline-tbh15b-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute -left-[10%] bottom-0 w-[120%] h-[180%] z-0 origin-top bg-tbh15b-liquid [rotate:6deg] translate-y-[120%] transition-[translate,rotate] duration-500 ease-[cubic-bezier(.5,0,.2,1)] group-hover/btn:[rotate:0deg] group-hover/btn:translate-y-0 group-focus-visible/btn:[rotate:0deg] group-focus-visible/btn:translate-y-0 motion-reduce:duration-[250ms]"></span>
<span class="relative z-[1]">Install now</span>
</button>
</section>
</div>@import "tailwindcss";
@theme {
--color-tbh15a-liquid: #4f86e0;
--color-tbh15a-liquid: oklch(0.6 0.18 235);
--color-tbh15a-stage: #0b1020;
--color-tbh15a-stage: oklch(0.14 0.03 265);
--color-tbh15b-liquid: #6fd99a;
--color-tbh15b-liquid: oklch(0.7 0.17 150);
--color-tbh15b-ink: #04180d;
--color-tbh15b-ink: oklch(0.2 0.05 150);
--color-tbh15b-stage: #08160e;
--color-tbh15b-stage: oklch(0.15 0.02 150);
--font-tbh15-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh15a-sway: tbh15a-sway 1.6s ease-in-out infinite;
}
/* Stays CSS: the sway keyframe. Ported from the original's margin-left animation to the
discrete translate property — same motion, no layout work per frame. */
@keyframes tbh15a-sway {
0%,
100% {
translate: 0 -100%;
}
50% {
translate: 22px -100%;
}
}
.tbh-15-group {
width: 100%;
min-height: 100svh;
}
.tbh-15a,
.tbh-15b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh15a-liquid: #4f86e0;
--color-tbh15a-liquid: oklch(0.6 0.18 235);
--color-tbh15a-stage: #0b1020;
--color-tbh15a-stage: oklch(0.14 0.03 265);
--color-tbh15b-liquid: #6fd99a;
--color-tbh15b-liquid: oklch(0.7 0.17 150);
--color-tbh15b-ink: #04180d;
--color-tbh15b-ink: oklch(0.2 0.05 150);
--color-tbh15b-stage: #08160e;
--color-tbh15b-stage: oklch(0.15 0.02 150);
--font-tbh15-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh15a-sway: tbh15a-sway 1.6s ease-in-out infinite;
}
/* Stays CSS: the sway keyframe. Ported from the original's margin-left animation to the
discrete translate property — same motion, no layout work per frame. */
@keyframes tbh15a-sway {
0%,
100% {
translate: 0 -100%;
}
50% {
translate: 22px -100%;
}
}
.tbh-15-group {
width: 100%;
min-height: 100svh;
}
.tbh-15a,
.tbh-15b {
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-tbh15a-liquid: #4f86e0;
--color-tbh15a-liquid: oklch(0.6 0.18 235);
--color-tbh15a-stage: #0b1020;
--color-tbh15a-stage: oklch(0.14 0.03 265);
--color-tbh15b-liquid: #6fd99a;
--color-tbh15b-liquid: oklch(0.7 0.17 150);
--color-tbh15b-ink: #04180d;
--color-tbh15b-ink: oklch(0.2 0.05 150);
--color-tbh15b-stage: #08160e;
--color-tbh15b-stage: oklch(0.15 0.02 150);
--font-tbh15-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh15a-sway: tbh15a-sway 1.6s ease-in-out infinite;
}
/* Stays CSS: the sway keyframe. Ported from the original's margin-left animation to the
discrete translate property — same motion, no layout work per frame. */
@keyframes tbh15a-sway {
0%,
100% {
translate: 0 -100%;
}
50% {
translate: 22px -100%;
}
}
.tbh-15-group {
width: 100%;
min-height: 100svh;
}
.tbh-15a,
.tbh-15b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh15a-liquid: #4f86e0;
--color-tbh15a-liquid: oklch(0.6 0.18 235);
--color-tbh15a-stage: #0b1020;
--color-tbh15a-stage: oklch(0.14 0.03 265);
--color-tbh15b-liquid: #6fd99a;
--color-tbh15b-liquid: oklch(0.7 0.17 150);
--color-tbh15b-ink: #04180d;
--color-tbh15b-ink: oklch(0.2 0.05 150);
--color-tbh15b-stage: #08160e;
--color-tbh15b-stage: oklch(0.15 0.02 150);
--font-tbh15-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh15a-sway: tbh15a-sway 1.6s ease-in-out infinite;
}
/* Stays CSS: the sway keyframe. Ported from the original's margin-left animation to the
discrete translate property — same motion, no layout work per frame. */
@keyframes tbh15a-sway {
0%,
100% {
translate: 0 -100%;
}
50% {
translate: 22px -100%;
}
}
.tbh-15-group {
width: 100%;
min-height: 100svh;
}
.tbh-15a,
.tbh-15b {
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 crest gradient is the entry's real content, and it ports verbatim into
before:bg-[radial-gradient(...)]withbg-[length:44px_22px] bg-repeat-x— two utilities for what most implementations reach for an SVG to do. - The sway keyframe changed technique in the port: the original animated
margin-left, which relayouts the layer every frame. Writing it astranslatecomposites instead, and because the keyframe must hold the risen-100%Y offset, it states both axes explicitly. - UI change from the CSS original: both designs now rise on
group-focus-visible/btn:as well as hover, and design A's reduced-motion path fills without the sway rather than dropping the effect.
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 Wave Fill Button Hover Effect
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-liquid-wave-fill-button-hover-effect/
A CTA that fills like a glass of water: on hover a coloured liquid rises from the bottom with a scalloped wave crest that sways — the bottom-up fill, distinct from entry 13's centre-out blob. The crest is a repeating radial-gradient, which is the trick worth stealing from this one.
## HTML
```html
<div class="tbh-15-group w-full min-h-svh flex flex-wrap">
<section class="tbh-15a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh15-sans bg-tbh15a-stage" aria-label="Liquid wave fill button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-xl border-2 border-tbh15a-liquid px-11 py-[18px] text-base font-extrabold text-tbh15a-liquid bg-transparent transition-colors duration-[350ms] delay-150 hover:text-white focus-visible:text-white focus-visible:outline-3 focus-visible:outline-tbh15a-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute -left-1/2 top-full w-[200%] h-full z-[1] bg-tbh15a-liquid translate-y-[22px] transition-[translate] duration-[550ms] ease-[cubic-bezier(.4,0,.2,1)] before:content-[''] before:absolute before:left-0 before:bottom-full before:w-full before:h-[22px] before:bg-[radial-gradient(circle_at_25%_0,transparent_12px,var(--color-tbh15a-liquid)_13px)] before:bg-[length:44px_22px] before:bg-repeat-x group-hover/btn:-translate-y-full group-hover/btn:animate-tbh15a-sway group-focus-visible/btn:-translate-y-full group-focus-visible/btn:animate-tbh15a-sway motion-reduce:transition-none motion-reduce:group-hover/btn:animate-none motion-reduce:group-focus-visible/btn:animate-none"></span>
<span class="relative z-[2]">Download</span>
</button>
</section>
<section class="tbh-15b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh15-sans bg-tbh15b-stage" aria-label="Tilted flood button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-full border-2 border-tbh15b-liquid px-[46px] py-[18px] text-base font-extrabold text-tbh15b-liquid bg-transparent transition-colors duration-300 delay-[120ms] hover:text-tbh15b-ink focus-visible:text-tbh15b-ink focus-visible:outline-3 focus-visible:outline-tbh15b-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute -left-[10%] bottom-0 w-[120%] h-[180%] z-0 origin-top bg-tbh15b-liquid [rotate:6deg] translate-y-[120%] transition-[translate,rotate] duration-500 ease-[cubic-bezier(.5,0,.2,1)] group-hover/btn:[rotate:0deg] group-hover/btn:translate-y-0 group-focus-visible/btn:[rotate:0deg] group-focus-visible/btn:translate-y-0 motion-reduce:duration-[250ms]"></span>
<span class="relative z-[1]">Install now</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh15a-liquid: #4f86e0;
--color-tbh15a-liquid: oklch(0.6 0.18 235);
--color-tbh15a-stage: #0b1020;
--color-tbh15a-stage: oklch(0.14 0.03 265);
--color-tbh15b-liquid: #6fd99a;
--color-tbh15b-liquid: oklch(0.7 0.17 150);
--color-tbh15b-ink: #04180d;
--color-tbh15b-ink: oklch(0.2 0.05 150);
--color-tbh15b-stage: #08160e;
--color-tbh15b-stage: oklch(0.15 0.02 150);
--font-tbh15-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh15a-sway: tbh15a-sway 1.6s ease-in-out infinite;
}
/* Stays CSS: the sway keyframe. Ported from the original's margin-left animation to the
discrete translate property — same motion, no layout work per frame. */
@keyframes tbh15a-sway {
0%,
100% {
translate: 0 -100%;
}
50% {
translate: 22px -100%;
}
}
.tbh-15-group {
width: 100%;
min-height: 100svh;
}
.tbh-15a,
.tbh-15b {
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 Wave Fill Button Hover Effect
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-liquid-wave-fill-button-hover-effect/
A CTA that fills like a glass of water: on hover a coloured liquid rises from the bottom with a scalloped wave crest that sways — the bottom-up fill, distinct from entry 13's centre-out blob. The crest is a repeating radial-gradient, which is the trick worth stealing from this one.
## HTML
```html
<div class="tbh-15-group w-full min-h-svh flex flex-wrap">
<section class="tbh-15a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh15-sans bg-tbh15a-stage" aria-label="Liquid wave fill button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-xl border-2 border-tbh15a-liquid px-11 py-[18px] text-base font-extrabold text-tbh15a-liquid bg-transparent transition-colors duration-[350ms] delay-150 hover:text-white focus-visible:text-white focus-visible:outline-3 focus-visible:outline-tbh15a-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute -left-1/2 top-full w-[200%] h-full z-[1] bg-tbh15a-liquid translate-y-[22px] transition-[translate] duration-[550ms] ease-[cubic-bezier(.4,0,.2,1)] before:content-[''] before:absolute before:left-0 before:bottom-full before:w-full before:h-[22px] before:bg-[radial-gradient(circle_at_25%_0,transparent_12px,var(--color-tbh15a-liquid)_13px)] before:bg-[length:44px_22px] before:bg-repeat-x group-hover/btn:-translate-y-full group-hover/btn:animate-tbh15a-sway group-focus-visible/btn:-translate-y-full group-focus-visible/btn:animate-tbh15a-sway motion-reduce:transition-none motion-reduce:group-hover/btn:animate-none motion-reduce:group-focus-visible/btn:animate-none"></span>
<span class="relative z-[2]">Download</span>
</button>
</section>
<section class="tbh-15b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh15-sans bg-tbh15b-stage" aria-label="Tilted flood button">
<button type="button" class="group/btn relative isolate overflow-hidden cursor-pointer rounded-full border-2 border-tbh15b-liquid px-[46px] py-[18px] text-base font-extrabold text-tbh15b-liquid bg-transparent transition-colors duration-300 delay-[120ms] hover:text-tbh15b-ink focus-visible:text-tbh15b-ink focus-visible:outline-3 focus-visible:outline-tbh15b-liquid focus-visible:outline-offset-[3px]">
<span aria-hidden="true" class="absolute -left-[10%] bottom-0 w-[120%] h-[180%] z-0 origin-top bg-tbh15b-liquid [rotate:6deg] translate-y-[120%] transition-[translate,rotate] duration-500 ease-[cubic-bezier(.5,0,.2,1)] group-hover/btn:[rotate:0deg] group-hover/btn:translate-y-0 group-focus-visible/btn:[rotate:0deg] group-focus-visible/btn:translate-y-0 motion-reduce:duration-[250ms]"></span>
<span class="relative z-[1]">Install now</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh15a-liquid: #4f86e0;
--color-tbh15a-liquid: oklch(0.6 0.18 235);
--color-tbh15a-stage: #0b1020;
--color-tbh15a-stage: oklch(0.14 0.03 265);
--color-tbh15b-liquid: #6fd99a;
--color-tbh15b-liquid: oklch(0.7 0.17 150);
--color-tbh15b-ink: #04180d;
--color-tbh15b-ink: oklch(0.2 0.05 150);
--color-tbh15b-stage: #08160e;
--color-tbh15b-stage: oklch(0.15 0.02 150);
--font-tbh15-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh15a-sway: tbh15a-sway 1.6s ease-in-out infinite;
}
/* Stays CSS: the sway keyframe. Ported from the original's margin-left animation to the
discrete translate property — same motion, no layout work per frame. */
@keyframes tbh15a-sway {
0%,
100% {
translate: 0 -100%;
}
50% {
translate: 22px -100%;
}
}
.tbh-15-group {
width: 100%;
min-height: 100svh;
}
.tbh-15a,
.tbh-15b {
min-height: 100svh;
}
```How this works
The crest is one gradient. A circle punched out of a solid colour, repeated every 44px, gives a scalloped waterline — no SVG, no mask, no image request. It sits on a before: layer at the top edge of the liquid, so it rises with it.
Rise, then sway. The lift is a transition (550ms, decelerating) and the sway is an animation applied only in the hovered state. Separating them is what keeps the water from wobbling while it is still climbing.
Design B settles two properties at once. A layer taller than the button, rotated 6deg and pushed below, arrives by returning rotate to 0 and translate to 0 — and because v4 keeps those separate, the resting tilt and the resting offset are independent values rather than one compound transform to keep in sync.
Make it yours
- Wave scale: the gradient's
12px/13pxradii and the44px_22pxtile — larger tiles read as ocean, smaller as fizz. - Fill speed:
duration-[550ms]on the liquid layer. - Sway amplitude: the
22pxin the sway keyframe; more reads as choppier water. - Partial fill: change the hover target from
-translate-y-fullto-translate-y-[60%]and the button becomes a progress indicator.
Gotchas — read before shipping
overflow-hiddenclips the liquid; without it the layer paints across the page as it rises.- The liquid layer must be wider than the button (
-left-1/2 w-[200%]) so the sway never exposes a dry edge. - The label must invert on hover and sit above the liquid (
relative z-[2]) — the crest is opaque and will swallow text that shares its stacking level. - The sway animates
translate, notmargin-leftas the CSS original did — a margin animation relayouts the wave every frame for no visual gain.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Repeating radial gradients and transforms are universal; with CSS unavailable each button is a plain outlined CTA.