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