All Taste components
Animated buttons
Ripple button
Buttons that answer a press with a ring of colour spreading from where you touched.
solid · outline · icon
Press and hold any of them
"use client";
/* globals.css
@keyframes ripple { from { transform: scale(0); opacity: .35; } to { transform: scale(1); opacity: 0; } }
@keyframes ripple-calm { from { opacity: .25; } to { opacity: 0; } }
*/
import { useState } from "react";
import { cn } from "@/lib/utils";
type Ripple = { id: number; x: number; y: number; size: number };
export function RippleButton({ className, children, onPointerDown, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
const [ripples, setRipples] = useState<Ripple[]>([]);
function press(e: React.PointerEvent<HTMLButtonElement>) {
const r = e.currentTarget.getBoundingClientRect();
const size = Math.max(r.width, r.height) * 2.2;
setRipples((rs) => [...rs, { id: e.timeStamp, x: e.clientX - r.left - size / 2, y: e.clientY - r.top - size / 2, size }]);
onPointerDown?.(e);
}
return (
<button
type="button"
onPointerDown={press}
className={cn(
"relative isolate inline-flex h-11 items-center justify-center gap-1.5 overflow-hidden rounded bg-primary px-5 text-[15px] font-medium text-primary-fg hover:bg-primary-hover",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className,
)}
{...props}
>
{children}
{ripples.map((r) => (
<span
key={r.id}
aria-hidden
onAnimationEnd={() => setRipples((rs) => rs.filter((x) => x.id !== r.id))}
className="pointer-events-none absolute rounded-full bg-current animate-[ripple_.65s_cubic-bezier(.2,.7,.1,1)_forwards] motion-reduce:animate-[ripple-calm_.2s_forwards]"
style={{ left: r.x, top: r.y, width: r.size, height: r.size }}
/>
))}
</button>
);
}
The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.