Back to library
INTERACTION_082

Liquid Hover Button.

A rounded fill rises from the bottom edge and floods the button. A transparent surface with a 1px border and the fill parked below the button before the trigger; translating the fill layer up to cover the surface while the label inverts once pointer enter on desktop, press on touch.

CATEGORY
Buttons
COMPLEXITY
Easy
TECH
CSS
PLATFORM
Both
PERFORMANCE
Light
LIVE PLAYGROUND
CSS / CSS
/* Liquid Hover Button — A rounded fill rises from the bottom edge and floods the button. */
.liquid-hover-button {
  --dur: 480ms;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --stagger: 60ms;
  will-change: transform, opacity;
}

.liquid-hover-button > * {
  opacity: 0;
  transform: translateY(14px);
  animation: liquid-hover-button-in var(--dur) var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}

/* a transparent surface with a 1px border and the fill parked below the button -> translating the fill layer up to cover the surface while the label inverts */
@keyframes liquid-hover-button-in {
  to {
    opacity: 1;
    transform: none;
    filter: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .liquid-hover-button > * {
    opacity: 1;
    transform: none;
    animation: none;
  }
}
React / TSX
import { useEffect, useRef } from "react";

/** Liquid Hover Button — trigger: pointer enter on desktop, press on touch */
export function LiquidHoverButton({ children }: { children: React.ReactNode }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      el.dataset["state"] = "in";
      return;
    }
    const io = new IntersectionObserver(
      ([entry]) => {
        if (entry?.isIntersecting) {
          el.dataset["state"] = "in";
          io.unobserve(el);
        }
      },
      { threshold: 0.2 },
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);

  return (
    <div ref={ref} className="liquid-hover-button" data-state="out">
      {children}
    </div>
  );
}

IF YOU LIKE THIS, STEAL THESE TOO.

INTERACTION_001CSS

Premium Button Lift

Weightless 2px rise with an edge highlight on hover.

Buttons / LightView
INTERACTION_002CSS

Shine Sweep Button

A diagonal chrome glint travels across the surface on hover.

Buttons / LightView
INTERACTION_004CSS

Sliding Arrow

Directional arrow that translates on hover.

Buttons / LightView
INTERACTION_005CSS

Text Swap

Label slides out while its duplicate slides in.

Buttons / LightView