CategoriesAllHeroNavigationFooterCall-to-ActionsFeature SectionsTestimonialsPricingContactGalleryStatsFAQServicesNewsletterAuthMiscellaneousButtons
All Components3D Cube InteractionBorder Logo GridCountdownFlip ButtonExpandable FAQsFlow ButtonGlow ButtonGrayscale CarouselInertia Zoom ParallaxLogo Cloud GridMobile Drawer NavigationPixel PreloaderProject GalleryRing ButtonShowcase CarouselSlide PreloaderSliding Stairs PreloaderStormtrooper FAQText Gradient OpacityThreads ButtonTilt HeadlineUltra Preloader
Code
app/page.tsx
1import RingButton from "@/app/components/buttons/ring-button";
2
3const HomePage = () => {
4    return (
5        <div className="w-screen h-screen bg-white items-center justify-center flex">
6            <RingButton text="Astrae's Amazing" />
7        </div>
8    );
9}
10
11export default HomePage;
12Installation
Initialized a project and installed the necessary dependencies to use this component.
1. Initialize a new Next.js project:
npx create-next-app@latest my-app2. Install Framer Motion
npm install framer-motionCopy & Paste Components
components/buttons/ring-button.tsx
1"use client"
2
3import { motion } from "framer-motion";
4import { useState } from "react";
5
6const RingButton = ({ text, fullWidth = false }: { text: string; fullWidth?: boolean }) => {
7    const [isHovered, setIsHovered] = useState(false)
8
9    const containerVariants = {
10        hidden: { opacity: 1 },
11        visible: {
12            opacity: 1,
13            transition: {
14                staggerChildren: 0.015,
15            },
16        },
17        exit: {
18            opacity: 1,
19            transition: {
20                staggerChildren: 0.015,
21            },
22        },
23    }
24
25    const charVariants = {
26        hidden: {
27            y: 0,
28        },
29        visible: {
30            y: -30,
31            transition: {
32                duration: 0.25,
33                ease: [0.25, 0.46, 0.45, 0.94] as const,
34            },
35        },
36        exit: {
37            y: 2,
38            transition: {
39                duration: 0.25,
40                ease: [0.25, 0.46, 0.45, 0.94] as const,
41            },
42        },
43    }
44
45    const charVariants2 = {
46        hidden: {
47            y: 30,
48        },
49        visible: {
50            y: 0,
51            transition: {
52                duration: 0.25,
53                ease: [0.25, 0.46, 0.45, 0.94] as const,
54            },
55        },
56        exit: {
57            y: 30,
58            transition: {
59                duration: 0.25,
60                ease: [0.25, 0.46, 0.45, 0.94] as const,
61            },
62        },
63    }
64
65    return (
66        <motion.button
67            className={`rounded-full flex items-center justify-center cursor-pointer 
68                        bg-[#002BBA] px-4 md:px-5 h-10 text-sm md:text-[15px] text-white 
69                        hover:ring-2 hover:ring-[#002BBA]/70 ring-offset-2 ring-offset-white 
70                        transition-all hover:scale-[1.02] ring-transparent 
71                        active:scale-[0.98] active:ring-[#002BBA] overflow-hidden 
72                        relative ${fullWidth ? 'w-full' : ''}`}
73            onMouseEnter={() => setIsHovered(true)}
74            onMouseLeave={() => setIsHovered(false)}
75            whileTap={{ scale: 0.98 }}
76        >
77            <span className="relative inline-block h-[1.6em] overflow-hidden">
78                <motion.span
79                    className="inline-flex"
80                    variants={containerVariants}
81                    initial="hidden"
82                    animate={isHovered ? "visible" : "exit"}
83                >
84                    {text.split("").map((char, index) => (
85                        <motion.span
86                            key={`first-${char}-${index}`}
87                            variants={charVariants}
88                            className="inline-block"
89                            style={{
90                                display: char === " " ? "inline" : "inline-block",
91                                whiteSpace: "pre"
92                            }}
93                        >
94                            {char}
95                        </motion.span>
96                    ))}
97                </motion.span>
98
99                <motion.span
100                    className="inline-flex absolute inset-0 items-center justify-center"
101                    variants={containerVariants}
102                    initial="hidden"
103                    animate={isHovered ? "visible" : "exit"}
104                >
105                    {text.split("").map((char, index) => (
106                        <motion.span
107                            key={`second-${char}-${index}`}
108                            variants={charVariants2}
109                            className="inline-block"
110                            style={{
111                                display: char === " " ? "inline" : "inline-block",
112                                whiteSpace: "pre"
113                            }}
114                        >
115                            {char}
116                        </motion.span>
117                    ))}
118                </motion.span>
119            </span>
120        </motion.button>
121    )
122}
123
124export default RingButton