Components
Animated Form

Animated Form

Animated form mimicking account creation with progressive name/password animation and completion checkmarks.

Create Account
Alex Morgan
********

Installation

1

Install the packages

npm i motion react-icons clsx tailwind-merge
2

Add util file

lib/util.ts
import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
3

Copy and paste the following code into your project

animated-form.tsx
"use client"; import { IoMdCheckmark } from "react-icons/io"; import { FaGithub } from "react-icons/fa6"; import { motion } from "motion/react"; import { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; type AnimatedFormProps = { delay?: number; name?: string; }; const AnimatedForm = ({ delay = 7000, name = "Alex Morgan", }: AnimatedFormProps) => { const [animationKey, setAnimationKey] = useState(0); const delayTime = Math.max(delay, 7000); useEffect(() => { const interval = setInterval(() => { setAnimationKey((prev) => prev + 1); }, delayTime); return () => clearInterval(interval); }, [delayTime]); return <Animatedform key={animationKey} name={name} />; }; export default AnimatedForm; const Animatedform = ({ name }: { name: string }) => { const password = "********"; const circleLength = 2 * Math.PI * 50; const nameAnimationDuration = Math.ceil(name.length / 5); const passwordAnimationDuration = 2; const nameStaggerDelay = nameAnimationDuration / name.length; const passwordStaggerDelay = passwordAnimationDuration / password.length; return ( <div className={cn("relative", "w-full max-w-[340px]")}> <div className="w-full rounded-[12px] border border-neutral-200/70 p-1.5 dark:border-neutral-900/70"> <div className="relative flex flex-col gap-1 divide-y divide-neutral-200 rounded-lg border border-neutral-200 dark:divide-neutral-900 dark:border-neutral-900"> <div className="bg-gradient-to-r from-neutral-700 to-neutral-300 bg-clip-text px-3 pb-2 pt-3 text-[14px] leading-[1rem] tracking-wide text-transparent dark:from-neutral-400 dark:to-neutral-700"> Create Account </div> <div className="flex flex-col gap-2 p-2"> <div className="flex w-full items-center justify-between gap-4 rounded-md border bg-gradient-to-r from-neutral-50 to-neutral-100 p-2 dark:from-neutral-900 dark:to-neutral-950"> <div className="text-xs"> {name.split("").map((char, index) => ( <motion.span key={`name-${index}`} className="inline-block font-[350]" initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.1, delay: index * nameStaggerDelay, ease: "easeOut", }} > {char === " " ? " " : char} </motion.span> ))} </div> <div className="relative"> <svg width="20" height="20" className="rotate-[-90deg]"> <motion.circle cx="10" cy="10" r="7" stroke="#22c55e" strokeWidth="2" fill="transparent" strokeDasharray={circleLength} strokeDashoffset={circleLength} animate={{ strokeDashoffset: 0 }} transition={{ duration: nameAnimationDuration * 3 + 1, ease: "easeInOut", delay: 0, }} /> <motion.circle cx="10" cy="10" r="7" fill="#22c55e" initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.2, delay: nameAnimationDuration + 0.1, }} /> </svg> <motion.div className="absolute inset-0 flex items-center justify-center text-background" initial={{ opacity: 0, scale: 0 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.2, delay: nameAnimationDuration + 0.2, }} > <IoMdCheckmark className="size-2.5" /> </motion.div> </div> </div> <div className="flex items-center justify-between gap-8 rounded-md border bg-background bg-gradient-to-r from-neutral-50 to-neutral-100 p-2 dark:from-neutral-900 dark:to-neutral-950"> <div className="font-mono text-xs"> {password.split("").map((char, index) => ( <motion.span key={`password-${index}`} className="inline-block" initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.1, delay: nameAnimationDuration + 0.5 + index * passwordStaggerDelay, ease: "easeOut", }} > {char} </motion.span> ))} </div> <div className="relative"> <svg width="20" height="20" className="rotate-[-90deg]"> <motion.circle cx="10" cy="10" r="7" stroke="#22c55e" strokeWidth="2" fill="transparent" strokeDasharray={circleLength} strokeDashoffset={circleLength} animate={{ strokeDashoffset: 0 }} transition={{ duration: 7, ease: "easeInOut", delay: nameAnimationDuration + 0.5, }} /> <motion.circle cx="10" cy="10" r="7" fill="#22c55e" initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.2, delay: nameAnimationDuration + passwordAnimationDuration + 0.6, }} /> </svg> <motion.div className="absolute inset-0 flex items-center justify-center text-background" initial={{ opacity: 0, scale: 0 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.2, delay: nameAnimationDuration + passwordAnimationDuration + 0.7, }} > <IoMdCheckmark className="size-2.5" /> </motion.div> </div> </div> <div className="h-[37.6px] rounded-md border bg-gradient-to-r from-neutral-50 to-neutral-100 opacity-40 dark:from-neutral-900 dark:to-neutral-950" /> </div> </div> </div> <div className="absolute bottom-0 left-0 h-[40px] w-full [background-image:linear-gradient(to_top,theme(colors.background)_60%,transparent_100%)]" /> <div className="absolute bottom-0 left-0 h-[100px] w-[12px] [background-image:linear-gradient(to_top,theme(colors.background)_60%,transparent_100%)]" /> <div className="absolute bottom-0 right-0 h-[100px] w-[12px] [background-image:linear-gradient(to_top,theme(colors.background)_60%,transparent_100%)]" /> <div className="absolute bottom-0 left-0 flex h-[50px] w-full items-center justify-around px-6"> <div className="rounded-full bg-gradient-to-b from-neutral-300 to-neutral-100 p-2 dark:from-neutral-700 dark:to-neutral-900"> <FaGithub className="size-6 text-primary" /> </div> <div className="rounded-full bg-gradient-to-b from-neutral-300 to-neutral-100 p-2 dark:from-neutral-700 dark:to-neutral-900"> <ShopifyIcon className="size-6" /> </div> <div className="rounded-full bg-gradient-to-b from-neutral-300 to-neutral-100 p-2 dark:from-neutral-700 dark:to-neutral-900"> <TwitchIcon className="size-6" /> </div> <div className="rounded-full bg-gradient-to-b from-neutral-300 to-neutral-100 p-2 dark:from-neutral-700 dark:to-neutral-900"> <YoutubeIcon className="size-6" /> </div> </div> </div> ); }; export const ShopifyIcon = (props: React.SVGProps<SVGSVGElement>) => ( <svg width="256" height="292" viewBox="0 0 256 292" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" fill="currentColor" {...props} > <path d="M223.774 57.34c-.201-1.46-1.48-2.268-2.537-2.357-1.055-.088-23.383-1.743-23.383-1.743s-15.507-15.395-17.209-17.099c-1.703-1.703-5.029-1.185-6.32-.805-.19.056-3.388 1.043-8.678 2.68-5.18-14.906-14.322-28.604-30.405-28.604-.444 0-.901.018-1.358.044C129.31 3.407 123.644.779 118.75.779c-37.465 0-55.364 46.835-60.976 70.635-14.558 4.511-24.9 7.718-26.221 8.133-8.126 2.549-8.383 2.805-9.45 10.462C21.3 95.806.038 260.235.038 260.235l165.678 31.042 89.77-19.42S223.973 58.8 223.775 57.34zM156.49 40.848l-14.019 4.339c.005-.988.01-1.96.01-3.023 0-9.264-1.286-16.723-3.349-22.636 8.287 1.04 13.806 10.469 17.358 21.32zm-27.638-19.483c2.304 5.773 3.802 14.058 3.802 25.238 0 .572-.005 1.095-.01 1.624-9.117 2.824-19.024 5.89-28.953 8.966 5.575-21.516 16.025-31.908 25.161-35.828zm-11.131-10.537c1.617 0 3.246.549 4.805 1.622-12.007 5.65-24.877 19.88-30.312 48.297l-22.886 7.088C75.694 46.16 90.81 10.828 117.72 10.828z" fill="#95BF46" /> <path d="M221.237 54.983c-1.055-.088-23.383-1.743-23.383-1.743s-15.507-15.395-17.209-17.099c-.637-.634-1.496-.959-2.394-1.099l-12.527 256.233 89.762-19.418S223.972 58.8 223.774 57.34c-.201-1.46-1.48-2.268-2.537-2.357" fill="#5E8E3E" /> <path d="M135.242 104.585l-11.069 32.926s-9.698-5.176-21.586-5.176c-17.428 0-18.305 10.937-18.305 13.693 0 15.038 39.2 20.8 39.2 56.024 0 27.713-17.577 45.558-41.277 45.558-28.44 0-42.984-17.7-42.984-17.7l7.615-25.16s14.95 12.835 27.565 12.835c8.243 0 11.596-6.49 11.596-11.232 0-19.616-32.16-20.491-32.16-52.724 0-27.129 19.472-53.382 58.778-53.382 15.145 0 22.627 4.338 22.627 4.338" fill="#FFF" /> </svg> ); export const TwitchIcon = (props: React.SVGProps<SVGSVGElement>) => ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2400 2800" xmlSpace="preserve" fill="currentColor" {...props} > <path fill="#fff" d="m2200 1300-400 400h-400l-350 350v-350H600V200h1600z" /> <g fill="#9146ff"> <path d="M500 0 0 500v1800h600v500l500-500h400l900-900V0H500zm1700 1300-400 400h-400l-350 350v-350H600V200h1600v1100z" /> <path d="M1700 550h200v600h-200zm-550 0h200v600h-200z" /> </g> </svg> ); export const YoutubeIcon = (props: React.SVGProps<SVGSVGElement>) => ( <svg viewBox="0 0 256 180" width={256} height={180} xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" fill="currentColor" {...props} > <path d="M250.346 28.075A32.18 32.18 0 0 0 227.69 5.418C207.824 0 127.87 0 127.87 0S47.912.164 28.046 5.582A32.18 32.18 0 0 0 5.39 28.24c-6.009 35.298-8.34 89.084.165 122.97a32.18 32.18 0 0 0 22.656 22.657c19.866 5.418 99.822 5.418 99.822 5.418s79.955 0 99.82-5.418a32.18 32.18 0 0 0 22.657-22.657c6.338-35.348 8.291-89.1-.164-123.134Z" fill="red" /> <path fill="#FFF" d="m102.421 128.06 66.328-38.418-66.328-38.418z" /> </svg> );
4

Update the import paths to match your project setup

Props

PropTypeDefaultDescription
namestringAlex MorganThe name to display in the animated form component.
delaynumber7000Time interval (in milliseconds) after which the animation restarts.