- On click, a ripple element is created at the pointer position inside the button.
- The ripple expands (scale animation) and fades out, then is removed from the DOM.
- The ripple must not affect button semantics or accessibility (use
pointer-events: none). - Implement in a reusable component so every interactive button in the app uses the same effect.
- To avoid hydration issues, create the ripple only on the client (e.g. in a
useEffector inside a client component that renders the ripple on click).
- User clicks the button.
- Get click coordinates relative to the button.
- Create a span (or div) with a class for the ripple, positioned at those coordinates.
- Animate: scale from 0 to a large value (e.g. 2–4), opacity from ~0.3 to 0.
- On animation end, remove the ripple element.
- Button content and behavior unchanged; ripple is purely visual.
- Use
position: absoluteon the ripple andposition: relativeon the button wrapper so the ripple stays inside the button. - Overflow: ensure the button has
overflow-hiddenso the ripple doesn’t spill out. - Prefer CSS transitions/animations or a small animation library (e.g. framer-motion) for the expand/fade.
- Do not use
any; type props and event handlers in TypeScript.
- An auto-playing shine sweep animates across the button surface, creating a "splashing" or glossy highlight effect.
- The effect runs continuously (infinite loop) and does not require user interaction.
- Use for primary CTA buttons (e.g. "Let's Get Started!", "Translate") to draw attention.
- Combines with the Ripple effect: the button uses
RippleButtonand gets both ripple-on-click and auto-shine.
- Wrap the
RippleButtonin a container with classcta-shine-wrap. - Add class
cta-shine-buttonto theRippleButtonitself. - The wrapper uses
::afterpseudo-element for a feathered white gradient that sweeps left-to-right. - Animation: translate the gradient from -100% to 200% with a slight skew, over ~4.5s, infinite.
- The wrapper must have
overflow: hiddenand matchingborder-radius(e.g.rounded-fullfor circular,rounded-3xlfor pill).
<div className="cta-shine-wrap rounded-full">
<RippleButton className="cta-shine-button w-14 h-14 ...">
<i className="fa-solid fa-language" />
</RippleButton>
</div>For pill-shaped buttons:
<div className="cta-shine-wrap">
<RippleButton className="cta-shine-button px-8 py-3 rounded-full ...">
Let's Get Started!
</RippleButton>
</div>.cta-shine-wrap:position: relative,display: inline-block,overflow: hidden,border-radius: 9999px..cta-shine-wrap::after: feathered white gradient,width: 85%,animation: cta-shine 4.5s cubic-bezier(0.35, 0, 0.15, 1) infinite.@keyframes cta-shine: translate from -100% to 200% withskewX(-8deg).