Animated Border Button
An animated outline button with dynamic border transitions, icon feedback, and smooth state changes from action to success.
Category
ButtonReact
CSS
shadcn
Manual
Create a file and paste the following code into it.
src/index-demo.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import { useState, useRef, useEffect } from "react";
import { AnimatePresence, motion } from "motion/react";
import { TextMorph } from "torph/react";
import * as Button from "@/components/ui/button";
import TrashIcon from "@/components/ui/icons/trash";
import SuccessIcon from "@/components/ui/icons/success";
export default function Demo() {
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const loadingTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
const handleSuccess = () => {
if (loading || success) return;
setLoading(true);
loadingTimeout.current = setTimeout(() => {
setLoading(false);
setSuccess(true);
}, 2000);
};
useEffect(() => {
if (!success) return;
const id = setTimeout(() => {
setSuccess(false);
}, 2000);
return () => clearTimeout(id);
}, [success]);
useEffect(() => {
return () => {
if (loadingTimeout.current) {
clearTimeout(loadingTimeout.current);
}
};
}, []);
return (
<section className="flex h-dvh w-full items-center justify-center gap-4 px-6">
<Button.Root
variant={success ? "success" : "error"}
mode="animatedBorder"
size="medium"
onClick={handleSuccess}
animateBorder={loading}
showAnimatedBorder={loading}
animatedBorderStyle={loading ? "dashed" : "solid"}
disabled={loading}
>
<AnimatePresence mode="popLayout">
<motion.div
key={success ? "success" : "remove"}
initial={false}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.4, y: 10 }}
transition={{ duration: 0.2, ease: "easeOut" }}
>
<Button.Icon
as={success ? SuccessIcon : TrashIcon}
className="size-5"
aria-hidden
/>
</motion.div>
</AnimatePresence>
<TextMorph>{success ? "Success" : "Remove"}</TextMorph>
</Button.Root>
</section>
);
}Update the import paths to match your project setup.