← all components
#04

Liquid fill button

On hover, a liquid rises from the bottom of the button with a sloshing wave crest, and the label flips to dark as it's submerged. Pure CSS and an SVG wave, no JavaScript. Available in React + Tailwind or plain HTML and CSS.

ReactTailwindCSSSVG
// LIVE DEMO
share this component →FacebookX / TwitterLinkedIn
// THE CODE
React + Tailwind
export default function LiquidFillButton() {
  return (
    <button className="liquid-btn relative px-9 py-4 rounded-2xl font-bold overflow-hidden border border-white/15">
      <span className="liquid-label relative z-20 transition-colors duration-300">Fill me up</span>
      <span className="liquid-fill absolute left-0 right-0 bottom-0 z-10">
        <svg className="liquid-wave absolute -top-3 left-0 w-[200%] h-4" viewBox="0 0 120 12" preserveAspectRatio="none">
          <path d="M0 6 Q 15 0 30 6 T 60 6 T 90 6 T 120 6 V12 H0 Z" fill="url(#lg)" />
          <defs><linearGradient id="lg" x1="0" x2="1">
            <stop offset="0" stopColor="#38bdf8" /><stop offset="1" stopColor="#818cf8" />
          </linearGradient></defs>
        </svg>
      </span>
      <style>{`
        .liquid-btn { color:#e5e7eb; background:#0d1420; }
        .liquid-btn .liquid-fill { height:0; background:linear-gradient(180deg,#38bdf8,#818cf8);
          transition:height .5s cubic-bezier(.4,0,.2,1); }
        .liquid-btn:hover .liquid-fill { height:130%; }
        .liquid-btn:hover .liquid-label { color:#05070d; }
        .liquid-wave { animation:liquid-slosh 1.6s linear infinite; }
        @keyframes liquid-slosh { to { transform:translateX(-50%); } }
        @media (prefers-reduced-motion: reduce) { .liquid-wave { animation:none; } }
      `}</style>
    </button>
  );
}
HTML + CSS
<!-- HTML -->
<button class="liquid-btn">
  <span class="label">Fill me up</span>
  <span class="fill">
    <svg class="wave" viewBox="0 0 120 12" preserveAspectRatio="none">
      <path d="M0 6 Q 15 0 30 6 T 60 6 T 90 6 T 120 6 V12 H0 Z" fill="url(#lg)"/>
      <defs><linearGradient id="lg" x1="0" x2="1"><stop offset="0" stop-color="#38bdf8"/><stop offset="1" stop-color="#818cf8"/></linearGradient></defs>
    </svg>
  </span>
</button>

<style>
.liquid-btn { position:relative; padding:16px 36px; border:1px solid rgba(255,255,255,.15); border-radius:16px;
  font:700 15px system-ui; color:#e5e7eb; background:#0d1420; overflow:hidden; cursor:pointer; }
.liquid-btn .label { position:relative; z-index:20; transition:color .3s; }
.liquid-btn .fill { position:absolute; left:0; right:0; bottom:0; z-index:10; height:0;
  background:linear-gradient(180deg,#38bdf8,#818cf8); transition:height .5s cubic-bezier(.4,0,.2,1); }
.liquid-btn:hover .fill { height:130%; }
.liquid-btn:hover .label { color:#05070d; }
.wave { position:absolute; top:-12px; left:0; width:200%; height:16px; animation:slosh 1.6s linear infinite; }
@keyframes slosh { to { transform:translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .wave { animation:none; } }
</style>
// HOW IT WORKS
  • 01A gradient fill element sits at the bottom of the button; on hover its height transitions from 0 to over 100%.
  • 02An SVG wave path rides the top of the fill and scrolls sideways on a loop, giving the liquid a sloshing crest.
  • 03The label's color flips as the liquid covers it. All CSS, and it stops animating under prefers-reduced-motion.
Want a custom component for your product?
Let's build it →