Circular Text

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

CODE • DESIGN • SHIP •

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,
    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}`] : "",
      ].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;
  }

  >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);
  }
}

3. Use the component

Now you can import and use the component anywhere in your project.

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;

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;

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).
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.