Back to snippets
tsx
v1.0.0
MIT
Animated Button Component
A customizable button with smooth hover animations and multiple variants
react
animation
ui
button
framer-motion
/**
* @title Animated Button Component
* @author TacoLabs
* @version 1.0.0
* @dependencies framer-motion, tailwindcss
* @created 2025-03-12
*/
import { motion } from 'framer-motion';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
"relative inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none overflow-hidden",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
ripple?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ripple = true, asChild = false, ...props }, ref) => {
const [rippleEffect, setRippleEffect] = useState<{ x: number; y: number; size: number } | null>(null);
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
if (!ripple) return;
const button = e.currentTarget;
const rect = button.getBoundingClientRect();
const size = Math.max(rect.width, rect.height) * 2;
setRippleEffect({
x: e.clientX - rect.left,
y: e.clientY - rect.top,
size,
});
setTimeout(() => setRippleEffect(null), 600);
};
return (
<motion.button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
onClick={handleClick}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
{...props}
>
{props.children}
{rippleEffect && (
<span
className="absolute bg-white/20 rounded-full pointer-events-none animate-ripple"
style={{
left: rippleEffect.x - rippleEffect.size / 2,
top: rippleEffect.y - rippleEffect.size / 2,
width: rippleEffect.size,
height: rippleEffect.size,
}}
/>
)}
</motion.button>
);
}
);
Button.displayName = "Button";
export { Button, buttonVariants };import { Button } from '@/components/ui/button'
// Basic usage
<Button>Click me</Button>
// With variants
<Button variant="destructive">Delete</Button>
<Button variant="outline">Cancel</Button>
// With sizes
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>
// With animation
<Button ripple={true}>With Ripple Effect</Button>Installation
To use this component, you need to install the following dependencies:
npm install framer-motion class-variance-authority tailwindcssSnippet Information
TA
TacoLabs
Author
Created
10 months agoStars
42Views
128Version
1.0.0License
MIT