Circular Text

A flexible component for rendering text along a circular path with customizable styling and rotation.

CODE • DESIGN • SHIP •

circular-text-preview.jsx

import CircularText from "@/components/text-effects/circular-text/circular-text";

const CircularTextPreview = () => {
  return (
    <CircularText
      text="CODE • DESIGN • SHIP •"
      radius={80}
      rotate={true}
    />
  );
};

export default CircularTextPreview;

Installation

Run the command from your project root directory (the folder that contains package.json).

Terminal / Console

npx mosaicui-cli@latest text-effects/circular-text

1. Copy the component file

Create a new file called circular-text.jsx in your reusable components folder (for example /src/components/) and paste the following code into it.

circular-text.jsx

import { memo } from "react";
import styles from "./circular-text.module.css";

const CircularText = (props) => {
  const {
    text,
    radius,
    addTrailingSpace = true,
    rotate = false,
    pauseOnHover = false,
    direction = "clockwise",
    duration = 10 * 1000,
    className,
    style = null,
    letterClassName,
    letterStyle = null,
    ...restProps
  } = props;

  const _text = text.trim() + (addTrailingSpace ? " " : "");
  const _radius = Math.max(0, radius);
  const _duration = Math.min(Math.max(100, duration), 60 * 1000);

  const getCoordinates = (angle, radius) => {
    const radians = +((Math.PI / 180) * angle).toPrecision(4);
    return {
      x: +((Math.cos(radians) * radius).toFixed(0)),
      y: +((Math.sin(radians) * radius).toFixed(0)),
    };
  };

  return (
    <span
      {...restProps}
      className={[
        className,
        styles["circular-text"],
        rotate ? styles[`rotate-${direction}`] : "",
        pauseOnHover ? styles["pause-on-hover"] : "",
      ].join(" ")}
      style={{
        ...style,
        "--radius": `${_radius}px`,
        "--duration": `${_duration}ms`,
      }}
    >
      {[..._text].map((letter, letterIndex) => {
        const angle = 360 / _text.length * letterIndex;
        const { x, y } = getCoordinates(angle, _radius);
        return (
          <span
            aria-hidden={true}
            key={`letter-${letter}-${letterIndex}`}
            className={letterClassName}
            style={{
              ...letterStyle,
              "--x": `${x}px`,
              "--y": `${y}px`,
              "--angle": `${angle}deg`,
            }}
          >
            {letter === " " ? <>&nbsp;</> : letter}
          </span>
        );
      })}
      <span
        className={styles["sr-only"]}
      >
        {text}
      </span>
    </span>
  );
};

export default memo(CircularText);

2. Copy the CSS module file

In the same folder, create a file called circular-text.module.css and paste the following CSS.

circular-text.module.css

.circular-text {
  position: relative;
  width: calc(var(--radius) * 2);
  height: calc(var(--radius) * 2);
  animation-duration: var(--duration);
  animation-timing-function: linear;
  animation-iteration-count: infinite;

  &.rotate-clockwise {
    animation-name: circular-text-keyframes;
  }

  &.rotate-anti-clockwise {
    animation-name: circular-text-keyframes;
    animation-direction: reverse;
  }

  &.pause-on-hover {
    &:hover {
      animation-play-state: paused;
    }
  }

  >span {
    position: absolute;
    top: 50%;
    left: 50%;
    transform-origin: 50% 50%;
    transform:
      translate(calc(-50% + var(--x)), calc(-50% + var(--y)))
      rotate(var(--angle));
  }

  > .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    animation: none;
  }
}

@keyframes circular-text-keyframes {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}

Examples

Discover how supported props transform components to match your needs.

Anti-Clockwise Direction

CODE • DESIGN • SHIP •

circular-text

import CircularText from "@/components/text-effects/circular-text/circular-text";

const CircularTextAntiClockwiseDirectionPreview = () => {
  return (
    <CircularText
      text="CODE • DESIGN • SHIP •"
      radius={85}
      rotate={true}
      direction="anti-clockwise"
    />
  );
};

export default CircularTextAntiClockwiseDirectionPreview;

Duration

CODE • DESIGN • SHIP •

circular-text

import CircularText from "@/components/text-effects/circular-text/circular-text";

const CircularTextDurationPreview = () => {
  return (
    <CircularText
      text="CODE • DESIGN • SHIP •"
      radius={85}
      rotate={true}
      duration={5000}
    />
  );
};

export default CircularTextDurationPreview;

Pause on Hover

CODE • DESIGN • SHIP •

circular-text

import CircularText from "@/components/text-effects/circular-text/circular-text";

const CircularTextPauseOnHoverPreview = () => {
  return (
    <CircularText
      text="CODE • DESIGN • SHIP •"
      radius={80}
      rotate={true}
      pauseOnHover={true}
    />
  );
};

export default CircularTextPauseOnHoverPreview;

Props

Configure the component with the following props:

PropTypeRequiredDefaultDescription
textstringYesThe text content to render around the circle.
radiusnumberYesRadius of the circle in pixels (px) used to position the letters.
addTrailingSpacebooleanNotrueAdds a trailing space after the text to improve spacing when looping around the circle.
rotatebooleanNofalseEnables continuous rotation animation of the circular text.
direction"clockwise" | "anti-clockwise"No"clockwise"Controls the rotation direction of the text around the circle.
durationnumberNo10000Duration of one full rotation in milliseconds (range: 10060000).
pauseOnHoverbooleanNofalsePauses the rotation animation when the user hovers over the component.
classNamestringNoOptional class name applied to the root circular text container.
styleReact.CSSPropertiesNoInline styles applied to the root container.
letterClassNamestringNoClass name applied to each individual letter element.
letterStyleReact.CSSPropertiesNoInline styles applied to each individual letter.