Ripple Button Component

An interactive button with hover-triggered ripple effects for tactile, responsive feedback

I built this Ripple Button as a micro-interaction experiment to make button hovers feel more alive and responsive. The goal was to create subtle motion that adds tactile feedback without overwhelming the user.
It's a client-side React component that tracks hover events and dynamically creates ripple elements at the cursor position. Each ripple expands outward with a smooth animation before fading away, creating the illusion of the button filling in with color.
The inspiration came from Material Design's ripple effects, but I wanted something softer and triggered on hover rather than click—making it feel more playful and less aggressive. The component is lightweight, reusable, and easy to customize with different colors or sizes.

Component Structure

The component uses React state to manage multiple ripples, allowing overlapping effects when the user moves their cursor across the button quickly:


const RippleButton = ({ children }: { children: React.ReactNode }) => {
  const [ripples, setRipples] = useState<Array<{ id: number; x: number; y: number }>>([]);

  const createRipple = (e: React.MouseEvent<HTMLButtonElement>) => {
    const button = e.currentTarget;
    const rect = button.getBoundingClientRect();
    
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    
    const newRipple = { id: Date.now(), x, y };
    setRipples(prev => [...prev, newRipple]);
    
    setTimeout(() => {
      setRipples(prev => prev.filter(r => r.id !== newRipple.id));
    }, 600);
  };

  return (
    <button
      onMouseEnter={createRipple}
      className="relative overflow-hidden px-8 py-4 rounded-xl border-2 border-blue-600 
                 text-blue-600 bg-white hover:bg-blue-600 hover:text-white 
                 hover:scale-[102%] transition-all duration-300"
    >
      {ripples.map(ripple => (
        <span
          key={ripple.id}
          className="absolute rounded-full bg-blue-500/30 animate-ripple"
          style={{
            left: ripple.x,
            top: ripple.y,
            width: '100px',
            height: '100px',
            marginLeft: '-50px',
            marginTop: '-50px',
          }}
        />
      ))}
      <span className="relative z-10">{children}</span>
    </button>
  );
};

Ripple Hover Effect

The ripple effect is created on hover, not click. This makes the interaction feel softer and more exploratory—users get feedback just by moving their cursor over the button.

When the user hovers, the cursor position is calculated relative to the button's bounds, and a new ripple element is inserted at that exact location:


const createRipple = (e: React.MouseEvent<HTMLButtonElement>) => {
  const button = e.currentTarget;
  const rect = button.getBoundingClientRect();
  const size = Math.max(rect.width, rect.height) * 2.4;
  
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  
  const newRipple = { id: Date.now(), x, y };
  setRipples(prev => [...prev, newRipple]);
  
  setTimeout(() => {
    setRipples(prev => prev.filter(r => r.id !== newRipple.id));
  }, 600);
};

The ripple size is calculated based on the button dimensions to ensure it always covers the entire button. Each ripple is tracked in state and automatically removed after the animation completes.

Ripple Animation CSS

The ripple animation scales the element from 0 to 4x its size while fading it out, creating a smooth, water-like expansion:


@keyframes ripple {
  0% {
    transform: scale(0);
    opacity: 1;
  }
  100% {
    transform: scale(4);
    opacity: 0;
  }
}

.animate-ripple {
  animation: ripple 0.6s ease-out;
}

The animation is applied using Tailwind's custom animation utilities. The pointer-events: none class ensures ripples don't interfere with button interactions.

Key Features

  • Hover-triggered ripple effects for subtle, playful interaction
  • Multiple overlapping ripples supported simultaneously
  • Smooth color and scale transitions using Tailwind CSS
  • Dynamic ripple sizing that adapts to button dimensions
  • Clean state management with automatic ripple cleanup

Here's the source code, I hope you like it :)

prash240303/crafts/RippleButton