Logo
Vaibhav Lab's

Circular Text

Animated circular text animation for modern UI layouts.

Last updated: February 13, 2026

Preview

VAIBHAV*LABS*COMPONENTS*
import CircularText from "../components/circular-text";export default function CircularTextDemo() {  return (    <CircularText      text="VAIBHAV*LABS*COMPONENTS*"      onHover="speedUp"      spinDuration={20}      className="custom-class"    />  );}

Features

  • Smooth circular text animation
  • Fully customizable text content
  • Lightweight and performant
  • Works with light and dark themes
  • Easy drop-in usage

Installation

npx shadcn@latest add https://vaibhavkesarwani.vercel.app/r/circular-text.json 
Import the dependecies
npm install motion
Copy and paste the following code into your project.
"use client";import {  motion,  MotionValue,  Transition,  useAnimation,  useMotionValue,} from "motion/react";import React, { useEffect } from "react";interface CircularTextProps {  text?: string;  spinDuration?: number;  onHover?: "slowDown" | "speedUp" | "pause" | "goBonkers";  className?: string;}const getRotationTransition = (  duration: number,  from: number,  loop: boolean = true,) => ({  from,  to: from + 360,  ease: "linear" as const,  duration,  type: "tween" as const,  repeat: loop ? Infinity : 0,});const getTransition = (duration: number, from: number) => ({  rotate: getRotationTransition(duration, from),  scale: {    type: "spring" as const,    damping: 20,    stiffness: 300,  },});const CircularText: React.FC<CircularTextProps> = ({  text = "VAIBHAV*LABS*COMPONENTS*",  spinDuration = 20,  onHover = "speedUp",  className = "",}) => {  const letters = Array.from(text);  const controls = useAnimation();  const rotation: MotionValue<number> = useMotionValue(0);  useEffect(() => {    const start = rotation.get();    controls.start({      rotate: start + 360,      scale: 1,      transition: getTransition(spinDuration, start),    });  }, [spinDuration, text, onHover, controls, rotation]);  const handleHoverStart = () => {    const start = rotation.get();    if (!onHover) return;    let transitionConfig: ReturnType<typeof getTransition> | Transition;    let scaleVal = 1;    switch (onHover) {      case "slowDown":        transitionConfig = getTransition(spinDuration * 2, start);        break;      case "speedUp":        transitionConfig = getTransition(spinDuration / 4, start);        break;      case "pause":        transitionConfig = {          rotate: { type: "spring", damping: 20, stiffness: 300 },          scale: { type: "spring", damping: 20, stiffness: 300 },        };        break;      case "goBonkers":        transitionConfig = getTransition(spinDuration / 20, start);        scaleVal = 0.8;        break;      default:        transitionConfig = getTransition(spinDuration, start);    }    controls.start({      rotate: start + 360,      scale: scaleVal,      transition: transitionConfig,    });  };  const handleHoverEnd = () => {    const start = rotation.get();    controls.start({      rotate: start + 360,      scale: 1,      transition: getTransition(spinDuration, start),    });  };  return (    <motion.div      className={`m-0 mx-auto rounded-full w-50 h-50 relative font-black text-white text-center cursor-pointer origin-center ${className}`}      style={{ rotate: rotation }}      initial={{ rotate: 0 }}      animate={controls}      onMouseEnter={handleHoverStart}      onMouseLeave={handleHoverEnd}    >      {letters.map((letter, i) => {        const rotationDeg = (360 / letters.length) * i;        const factor = Math.PI / letters.length;        const x = factor * i;        const y = factor * i;        const transform = `rotateZ(${rotationDeg}deg) translate3d(${x}px, ${y}px, 0)`;        return (          <span            key={i}            className="absolute inline-block inset-0 text-2xl transition-all duration-500 ease-[cubic-bezier(0,0,0,1)]"            style={{ transform, WebkitTransform: transform }}          >            {letter}          </span>        );      })}    </motion.div>  );};export default CircularText;
Update the import paths to match your project setup.

Usage

import CircularText from "@/components/ui/circular-text";
<CircularText
  text="VAIBHAV*LABS*COMPONENTS*"
  onHover="speedUp"
  spinDuration={20}
  className="custom-class"
/>

Props

Prop

Type

Dependencies