Circle Menu
Dialog Form
Dominoes List Scroll
Dominoes Scroll Indicator
Eagle Vision
Electric AI Input
File Input
Flip Scroll
Glowing Scroll Indicator
Horizontal Scroll
Icon Wheel
Image Pile
Interactive CTA
Interactive Folder
Interest Picker
Jelly Loader
Leave Rating
Mask Cursor Effect
Magnet Tabs
Masonry Grid
OTP Input
Photo Gallery
Pixelated Carousel
Rolling Ball Scroll Indicator
Rubik Cube
Sidebar
Sine Wave
Skeumorphic Music Card
Social Media Card
Stacked Input Form
Stack Scroll
Trading Card
Hold Button
A button that requires users to hold it down for a specified duration before triggering an action. Perfect for preventing accidental clicks on important operations like payments or deletions.
Hold Button
Create a file hold-button.tsx in your components folder and paste this code
'use client';
import { Check, Trash } from 'lucide-react';
import { AnimatePresence, motion } from 'framer-motion';
import { useState } from 'react';
const HoldButton = ({
children,
variant = 'default',
icon = null,
onClick,
holdDuration = 1000,
successText = 'Confirmed'
}: {
children: React.ReactNode;
variant?: 'default' | 'success' | 'destructive';
icon?: React.ReactNode;
onClick?: () => void;
holdDuration?: number;
successText?: string;
}) => {
const [isSuccess, setIsSuccess] = useState(false);
const [intervalId, setIntervalId] = useState<NodeJS.Timeout | null>(null);
const handleMouseDown = () => {
setIntervalId(
setTimeout(() => {
setIsSuccess(true);
if (onClick) {
onClick();
}
setTimeout(() => setIsSuccess(false), 600);
}, holdDuration)
);
};
const handleMouseUp = () => {
if (intervalId) {
clearTimeout(intervalId);
setIntervalId(null);
}
};
const variants = {
default: {
color: '#007BFF',
backgroundColor: '#CCE5FF',
icon: <Check size={18} />
},
success: {
color: '#4CAF50',
backgroundColor: '#C8E6C9',
icon: <Check size={18} />
},
destructive: {
color: '#E5484D',
backgroundColor: '#FFDBDC',
icon: <Trash size={18} />
}
};
return (
<>
<style>
{`
.button {
position: relative;
display: flex;
height: 40px;
align-items: center;
gap: 8px;
border-radius: 9999px;
padding-inline: 24px;
}
.overlay-btn {
position: absolute;
top: 0px;
clip-path: inset(0% 100% 0% 0%);
}
.wrapper {
transition: 0.15s ease-out;
}
.wrapper:active .overlay-btn {
clip-path: inset(0% 0% 0% 0%);
transition: 1000ms ease-in-out;
}
.wrapper:active {
scale: 0.97;
}
`}
</style>
<div onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} className="relative wrapper">
<button className="button trigger-btn hover:bg-zinc-800 bg-zinc-700 text-white">
{icon || variants[variant].icon}
{children}
</button>
<button
style={{
color: variants[variant].color,
backgroundColor: variants[variant].backgroundColor,
transition: intervalId ? `${holdDuration}ms ease-out` : '0.2s ease-out'
}}
className="button overlay-btn w-full"
>
<div className="shrink-0">{icon || variants[variant].icon}</div>
<div className="relative w-full">
<AnimatePresence mode="popLayout">
{!isSuccess ? (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="whitespace-nowrap h-full"
>
{children}
</motion.div>
) : (
<motion.p
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="whitespace-nowrap h-full text-left"
>
{successText}
</motion.p>
)}
</AnimatePresence>
</div>
</button>
</div>
</>
);
};
export default HoldButton;Usage
<div className="flex flex-col gap-4 items-center justify-center">
<HoldButton
variant="default"
onClick={() => {
console.log('Confirmed');
}}
>
Hold to Confirm
</HoldButton>
<HoldButton
variant="success"
successText="Success"
icon={<BadgeDollarSign size={18} />}
>
Hold to Pay Now
</HoldButton>
<HoldButton variant="destructive" successText="Deleted" holdDuration={2000}>
Hold to Delete
</HoldButton>
</div>