06 / 20
All 20 in 20 Tailwind CSS Animated Buttons
- 01Tailwind Neon Glow Button with Pulsing Halo
- 02Tailwind Background Slide Hover Button (Four Directions)
- 03Tailwind Ripple Effect Button with No JavaScript
- 04Tailwind Animated Border Draw Button with Staged Delays
- 05Tailwind 3D Press Effect Button with Keycap Depth
- 06Tailwind Liquid Fill Button with Animated Wave Crest
- 07Tailwind Moving Gradient CTA Button
- 08Tailwind Shine Sweep Hover Button with Gloss Streak
- 09Tailwind Jelly Bounce Button with Elastic Squash
- 10Tailwind Text Flip Button Hover with 3D Rotation
- 11Tailwind Animated CTA Hero Button with Beacon Rings & Particle Burst
- 12Tailwind Submit Button Success State with Checkmark Draw
- 13Tailwind Animated Download Button with Progress Fill
- 14Tailwind Hamburger to Close Button with aria-expanded Variants
- 15Tailwind Add to Cart Button with Flying Product Dot
- 16Tailwind Social Share Buttons with Brand Hover & Copy Link
- 17Tailwind Animated Toggle Switch with peer-checked & has-checked
- 18Tailwind Loading Spinner Inside Button with State Machine
- 19Tailwind Next & Prev Arrow Buttons with Chasing Icons
- 20Tailwind Ghost Button with Cursor-Aware Background Reveal
Tailwind Liquid Fill Button with Animated Wave Crest
Liquid rises to fill the button on hover, its surface undulating while it sits there. Two motions, two mechanisms: a transition for the rise and a keyframe for the wave — and the port makes the split explicit, since the rise is a utility pair and the wave is hover:before:animate-tab06-wave.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tab-06 w-full min-h-svh grid place-items-center bg-tab06-bg font-tab06-sans px-6 py-10">
<div class="flex flex-col items-center gap-10">
<p class="text-[0.78rem] tracking-[0.14em] uppercase text-tab06-mut m-0">Hover to fill</p>
<div class="flex flex-wrap justify-center gap-5">
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-cyan-ink bg-transparent border-2 border-tab06-cyan-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-cyan-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-cyan before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Explore</span></button>
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-violet-ink bg-transparent border-2 border-tab06-violet-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-violet-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-violet before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Subscribe</span></button>
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-amber-ink bg-transparent border-2 border-tab06-amber-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-amber-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-amber before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Upgrade</span></button>
</div>
</div>
</section><section class="tab-06 w-full min-h-svh grid place-items-center bg-tab06-bg font-tab06-sans px-6 py-10">
<div class="flex flex-col items-center gap-10">
<p class="text-[0.78rem] tracking-[0.14em] uppercase text-tab06-mut m-0">Hover to fill</p>
<div class="flex flex-wrap justify-center gap-5">
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-cyan-ink bg-transparent border-2 border-tab06-cyan-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-cyan-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-cyan before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Explore</span></button>
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-violet-ink bg-transparent border-2 border-tab06-violet-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-violet-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-violet before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Subscribe</span></button>
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-amber-ink bg-transparent border-2 border-tab06-amber-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-amber-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-amber before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Upgrade</span></button>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-tab06-bg: #0f172a;
--color-tab06-bg: oklch(0.21 0.04 258);
--color-tab06-mut: #94a3b8;
--color-tab06-mut: oklch(0.72 0.03 255);
--color-tab06-cyan: #0e7490;
--color-tab06-cyan: oklch(0.48 0.1 221);
--color-tab06-cyan-edge: #22d3ee;
--color-tab06-cyan-edge: oklch(0.79 0.14 202);
--color-tab06-cyan-ink: #a5f3fc;
--color-tab06-cyan-ink: oklch(0.9 0.08 202);
--color-tab06-violet: #6d28d9;
--color-tab06-violet: oklch(0.47 0.24 295);
--color-tab06-violet-edge: #a78bfa;
--color-tab06-violet-edge: oklch(0.74 0.15 292);
--color-tab06-violet-ink: #ddd6fe;
--color-tab06-violet-ink: oklch(0.89 0.06 292);
--color-tab06-amber: #b45309;
--color-tab06-amber: oklch(0.53 0.15 62);
--color-tab06-amber-edge: #fbbf24;
--color-tab06-amber-edge: oklch(0.84 0.16 84);
--color-tab06-amber-ink: #fef3c7;
--color-tab06-amber-ink: oklch(0.95 0.05 90);
--font-tab06-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab06-wave: tab06-wave 2.5s ease-in-out infinite 0.65s;
}
@keyframes tab06-wave {
0%,
100% {
border-radius: 45% 55% 0 0 / 25% 25% 0 0;
}
50% {
border-radius: 55% 45% 0 0 / 20% 30% 0 0;
}
}
.tab-06 {
width: 100%;
min-height: 100svh;
}
.tab-06 ::selection {
background: var(--color-tab06-cyan-edge);
color: #0b1220;
}@import "tailwindcss";
@theme {
--color-tab06-bg: #0f172a;
--color-tab06-bg: oklch(0.21 0.04 258);
--color-tab06-mut: #94a3b8;
--color-tab06-mut: oklch(0.72 0.03 255);
--color-tab06-cyan: #0e7490;
--color-tab06-cyan: oklch(0.48 0.1 221);
--color-tab06-cyan-edge: #22d3ee;
--color-tab06-cyan-edge: oklch(0.79 0.14 202);
--color-tab06-cyan-ink: #a5f3fc;
--color-tab06-cyan-ink: oklch(0.9 0.08 202);
--color-tab06-violet: #6d28d9;
--color-tab06-violet: oklch(0.47 0.24 295);
--color-tab06-violet-edge: #a78bfa;
--color-tab06-violet-edge: oklch(0.74 0.15 292);
--color-tab06-violet-ink: #ddd6fe;
--color-tab06-violet-ink: oklch(0.89 0.06 292);
--color-tab06-amber: #b45309;
--color-tab06-amber: oklch(0.53 0.15 62);
--color-tab06-amber-edge: #fbbf24;
--color-tab06-amber-edge: oklch(0.84 0.16 84);
--color-tab06-amber-ink: #fef3c7;
--color-tab06-amber-ink: oklch(0.95 0.05 90);
--font-tab06-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab06-wave: tab06-wave 2.5s ease-in-out infinite 0.65s;
}
@keyframes tab06-wave {
0%,
100% {
border-radius: 45% 55% 0 0 / 25% 25% 0 0;
}
50% {
border-radius: 55% 45% 0 0 / 20% 30% 0 0;
}
}
.tab-06 {
width: 100%;
min-height: 100svh;
}
.tab-06 ::selection {
background: var(--color-tab06-cyan-edge);
color: #0b1220;
}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-tab06-bg: #0f172a;
--color-tab06-bg: oklch(0.21 0.04 258);
--color-tab06-mut: #94a3b8;
--color-tab06-mut: oklch(0.72 0.03 255);
--color-tab06-cyan: #0e7490;
--color-tab06-cyan: oklch(0.48 0.1 221);
--color-tab06-cyan-edge: #22d3ee;
--color-tab06-cyan-edge: oklch(0.79 0.14 202);
--color-tab06-cyan-ink: #a5f3fc;
--color-tab06-cyan-ink: oklch(0.9 0.08 202);
--color-tab06-violet: #6d28d9;
--color-tab06-violet: oklch(0.47 0.24 295);
--color-tab06-violet-edge: #a78bfa;
--color-tab06-violet-edge: oklch(0.74 0.15 292);
--color-tab06-violet-ink: #ddd6fe;
--color-tab06-violet-ink: oklch(0.89 0.06 292);
--color-tab06-amber: #b45309;
--color-tab06-amber: oklch(0.53 0.15 62);
--color-tab06-amber-edge: #fbbf24;
--color-tab06-amber-edge: oklch(0.84 0.16 84);
--color-tab06-amber-ink: #fef3c7;
--color-tab06-amber-ink: oklch(0.95 0.05 90);
--font-tab06-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab06-wave: tab06-wave 2.5s ease-in-out infinite 0.65s;
}
@keyframes tab06-wave {
0%,
100% {
border-radius: 45% 55% 0 0 / 25% 25% 0 0;
}
50% {
border-radius: 55% 45% 0 0 / 20% 30% 0 0;
}
}
.tab-06 {
width: 100%;
min-height: 100svh;
}
.tab-06 ::selection {
background: var(--color-tab06-cyan-edge);
color: #0b1220;
}@import "tailwindcss";
@theme {
--color-tab06-bg: #0f172a;
--color-tab06-bg: oklch(0.21 0.04 258);
--color-tab06-mut: #94a3b8;
--color-tab06-mut: oklch(0.72 0.03 255);
--color-tab06-cyan: #0e7490;
--color-tab06-cyan: oklch(0.48 0.1 221);
--color-tab06-cyan-edge: #22d3ee;
--color-tab06-cyan-edge: oklch(0.79 0.14 202);
--color-tab06-cyan-ink: #a5f3fc;
--color-tab06-cyan-ink: oklch(0.9 0.08 202);
--color-tab06-violet: #6d28d9;
--color-tab06-violet: oklch(0.47 0.24 295);
--color-tab06-violet-edge: #a78bfa;
--color-tab06-violet-edge: oklch(0.74 0.15 292);
--color-tab06-violet-ink: #ddd6fe;
--color-tab06-violet-ink: oklch(0.89 0.06 292);
--color-tab06-amber: #b45309;
--color-tab06-amber: oklch(0.53 0.15 62);
--color-tab06-amber-edge: #fbbf24;
--color-tab06-amber-edge: oklch(0.84 0.16 84);
--color-tab06-amber-ink: #fef3c7;
--color-tab06-amber-ink: oklch(0.95 0.05 90);
--font-tab06-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab06-wave: tab06-wave 2.5s ease-in-out infinite 0.65s;
}
@keyframes tab06-wave {
0%,
100% {
border-radius: 45% 55% 0 0 / 25% 25% 0 0;
}
50% {
border-radius: 55% 45% 0 0 / 20% 30% 0 0;
}
}
.tab-06 {
width: 100%;
min-height: 100svh;
}
.tab-06 ::selection {
background: var(--color-tab06-cyan-edge);
color: #0b1220;
}<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.
- Rise stays a transition, wave stays a keyframe, and the class list names both. Collapsing them into one animation would have cost the interruptible, reverse-on-leave behaviour that makes the rise feel like liquid rather than a cutscene.
- The animation's start delay lives in the
@themetoken, not the markup — the only place it can be guaranteed to match the rise duration. - UI change from the CSS original: darker liquid with lighter label text, which lifts the mid-fill contrast (the original passed at rest and dipped while filling), plus focus parity on every variant.
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 Button with Animated Wave Crest
Source: https://codefronts.com/motion/tailwind-css-animated-buttons/tailwind-liquid-fill-button-animation/
Liquid rises to fill the button on hover, its surface undulating while it sits there. Two motions, two mechanisms: a transition for the rise and a keyframe for the wave — and the port makes the split explicit, since the rise is a utility pair and the wave is hover:before:animate-tab06-wave.
## HTML
```html
<section class="tab-06 w-full min-h-svh grid place-items-center bg-tab06-bg font-tab06-sans px-6 py-10">
<div class="flex flex-col items-center gap-10">
<p class="text-[0.78rem] tracking-[0.14em] uppercase text-tab06-mut m-0">Hover to fill</p>
<div class="flex flex-wrap justify-center gap-5">
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-cyan-ink bg-transparent border-2 border-tab06-cyan-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-cyan-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-cyan before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Explore</span></button>
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-violet-ink bg-transparent border-2 border-tab06-violet-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-violet-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-violet before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Subscribe</span></button>
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-amber-ink bg-transparent border-2 border-tab06-amber-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-amber-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-amber before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Upgrade</span></button>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tab06-bg: #0f172a;
--color-tab06-bg: oklch(0.21 0.04 258);
--color-tab06-mut: #94a3b8;
--color-tab06-mut: oklch(0.72 0.03 255);
--color-tab06-cyan: #0e7490;
--color-tab06-cyan: oklch(0.48 0.1 221);
--color-tab06-cyan-edge: #22d3ee;
--color-tab06-cyan-edge: oklch(0.79 0.14 202);
--color-tab06-cyan-ink: #a5f3fc;
--color-tab06-cyan-ink: oklch(0.9 0.08 202);
--color-tab06-violet: #6d28d9;
--color-tab06-violet: oklch(0.47 0.24 295);
--color-tab06-violet-edge: #a78bfa;
--color-tab06-violet-edge: oklch(0.74 0.15 292);
--color-tab06-violet-ink: #ddd6fe;
--color-tab06-violet-ink: oklch(0.89 0.06 292);
--color-tab06-amber: #b45309;
--color-tab06-amber: oklch(0.53 0.15 62);
--color-tab06-amber-edge: #fbbf24;
--color-tab06-amber-edge: oklch(0.84 0.16 84);
--color-tab06-amber-ink: #fef3c7;
--color-tab06-amber-ink: oklch(0.95 0.05 90);
--font-tab06-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab06-wave: tab06-wave 2.5s ease-in-out infinite 0.65s;
}
@keyframes tab06-wave {
0%,
100% {
border-radius: 45% 55% 0 0 / 25% 25% 0 0;
}
50% {
border-radius: 55% 45% 0 0 / 20% 30% 0 0;
}
}
.tab-06 {
width: 100%;
min-height: 100svh;
}
.tab-06 ::selection {
background: var(--color-tab06-cyan-edge);
color: #0b1220;
}
```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 Button with Animated Wave Crest
Source: https://codefronts.com/motion/tailwind-css-animated-buttons/tailwind-liquid-fill-button-animation/
Liquid rises to fill the button on hover, its surface undulating while it sits there. Two motions, two mechanisms: a transition for the rise and a keyframe for the wave — and the port makes the split explicit, since the rise is a utility pair and the wave is hover:before:animate-tab06-wave.
## HTML
```html
<section class="tab-06 w-full min-h-svh grid place-items-center bg-tab06-bg font-tab06-sans px-6 py-10">
<div class="flex flex-col items-center gap-10">
<p class="text-[0.78rem] tracking-[0.14em] uppercase text-tab06-mut m-0">Hover to fill</p>
<div class="flex flex-wrap justify-center gap-5">
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-cyan-ink bg-transparent border-2 border-tab06-cyan-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-cyan-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-cyan before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Explore</span></button>
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-violet-ink bg-transparent border-2 border-tab06-violet-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-violet-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-violet before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Subscribe</span></button>
<button type="button" class="relative overflow-hidden min-h-11 px-9 text-base font-bold tracking-[0.03em] text-tab06-amber-ink bg-transparent border-2 border-tab06-amber-edge rounded-[10px] cursor-pointer transition-colors duration-[400ms] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab06-amber-edge focus-visible:text-white before:content-[''] before:absolute before:bottom-0 before:-left-1/4 before:w-[150%] before:h-[200%] before:bg-tab06-amber before:[border-radius:45%_55%_0_0/25%_25%_0_0] before:translate-y-full before:transition-transform before:duration-[650ms] before:ease-[cubic-bezier(0.4,0,0.2,1)] hover:before:translate-y-[5%] hover:before:animate-tab06-wave focus-visible:before:translate-y-[5%] motion-reduce:before:transition-none motion-reduce:hover:before:animate-none"><span class="relative z-10">Upgrade</span></button>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tab06-bg: #0f172a;
--color-tab06-bg: oklch(0.21 0.04 258);
--color-tab06-mut: #94a3b8;
--color-tab06-mut: oklch(0.72 0.03 255);
--color-tab06-cyan: #0e7490;
--color-tab06-cyan: oklch(0.48 0.1 221);
--color-tab06-cyan-edge: #22d3ee;
--color-tab06-cyan-edge: oklch(0.79 0.14 202);
--color-tab06-cyan-ink: #a5f3fc;
--color-tab06-cyan-ink: oklch(0.9 0.08 202);
--color-tab06-violet: #6d28d9;
--color-tab06-violet: oklch(0.47 0.24 295);
--color-tab06-violet-edge: #a78bfa;
--color-tab06-violet-edge: oklch(0.74 0.15 292);
--color-tab06-violet-ink: #ddd6fe;
--color-tab06-violet-ink: oklch(0.89 0.06 292);
--color-tab06-amber: #b45309;
--color-tab06-amber: oklch(0.53 0.15 62);
--color-tab06-amber-edge: #fbbf24;
--color-tab06-amber-edge: oklch(0.84 0.16 84);
--color-tab06-amber-ink: #fef3c7;
--color-tab06-amber-ink: oklch(0.95 0.05 90);
--font-tab06-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab06-wave: tab06-wave 2.5s ease-in-out infinite 0.65s;
}
@keyframes tab06-wave {
0%,
100% {
border-radius: 45% 55% 0 0 / 25% 25% 0 0;
}
50% {
border-radius: 55% 45% 0 0 / 20% 30% 0 0;
}
}
.tab-06 {
width: 100%;
min-height: 100svh;
}
.tab-06 ::selection {
background: var(--color-tab06-cyan-edge);
color: #0b1220;
}
```How this works
Rise and ripple are separate systems. The rise is transition-transform from translate-y-full to translate-y-[5%] — interruptible, reversible, compositor-only. The ripple is a keyframe on border-radius, which cannot be expressed as a transition between two hover states. The port keeps both and names each at the element.
The delay is in the token. --animate-tab06-wave: tab06-wave 2.5s ease-in-out infinite 0.65s carries the offset that matches the rise duration, so the class list stays one word and the two timings can never fall out of sync in the markup.
The crest is an arbitrary property, correctly. An eight-value elliptical border-radius is not a design-scale value; before:[border-radius:…] with underscore escapes is the idiomatic answer and carries the slash syntax through intact.
Make it yours
- Gradient liquid:
before:bg-[linear-gradient(180deg,#06b6d4,#0284c7)]. - Slower, more dramatic fill:
before:duration-[650ms]→before:duration-1000(and raise the wave's delay to match). - Choppier surface: push the crest percentages further apart in both the class and the keyframe.
Gotchas — read before shipping
overflow-hiddenclips the liquid and any outershadow-*. Wrap the button if you want a glow as well.- The wave animates
border-radius, which paints rather than composites. One button is free; forty in a scrolling list are not. - Firefox can collapse a pseudo-element at very high radius percentages unless its width and height are explicit — which is why the layer is sized
150% / 200%rather thaninset-0.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Border-radius animation is universally supported; the floor is Tailwind v4's generated sheet.