Rolling Letters

Displays a word where each character animates with a vertical rolling effect, creating a dynamic text reveal.

1. Copy the component file

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

rolling-letters-animation.jsx

import { memo, useMemo } from "react";
import styles from "./rolling-letters-animation.module.css";

const RollingLettersAnimation = (props) => {
  const {
    word,
    letterBlockSize = 16,
    duration = 2000,
    className,
    style,
    letterClassName,
    letterStyle,
    ...restProps
  } = props;

  const _duration = Math.max(100, duration);

  const upperCaseLetterSet = useMemo(() => (
    Array.from({
      length: 26,
    }).map((_, i) => (
      String.fromCharCode(65 + i)
    ))
  ), []);

  const lowerCaseLetterSet = useMemo(() => (
    Array.from({
      length: 26,
    }).map((_, i) => (
      String.fromCharCode(97 + i)
    ))
  ), []);

  const getRandomSeries = (exceptLetter) => {
    const isUpperCase = exceptLetter === exceptLetter.toUpperCase();
    const letterSet = isUpperCase ? upperCaseLetterSet : lowerCaseLetterSet;
    const lettersExceptGivenLetter = letterSet.filter(l => l !== exceptLetter);
    const shuffledLetterSet = lettersExceptGivenLetter.sort(() => Math.random() - Math.random());
    return shuffledLetterSet;
  };

  return (
    <span
      {...restProps}
      className={[
        className,
        styles["rolling-letters-animation"],
      ].join(" ")}
      style={{
        ...style,
        "--animation-duration": `${_duration}ms`,
        "--letter-block-size": `${letterBlockSize}px`,
      }}
    >
      {[...word].map((letter, letterIndex) => {
        const series = getRandomSeries(letter);
        const isReverse = (letterIndex % 2) === 0;
        if (isReverse) {
          series.unshift(letter);
        } else {
          series.push(letter);
        }
        return (
          <span
            aria-hidden={true}
            key={`letter-column-${letter}-${letterIndex}`}
            className={styles["letters-wrapper"]}
            style={{
              "--total-rolling-letters": `${series.length - 1}`,
            }}
          >
            {series.map((l, i) => (
              <span
                aria-hidden={true}
                key={`letter-block-${l}-${i}`}
                className={[
                  letterClassName,
                  styles["letter"],
                ].join(" ")}
                style={letterStyle}
              >
                {l}
              </span>
            ))}
          </span>
        )
      })}
      <span
        className={styles["sr-only"]}
      >
        {word}
      </span>
    </span>
  );
};

export default memo(RollingLettersAnimation);

2. Copy the CSS module file

In the same folder, create a file called rolling-letters-animation.module.css and paste the following CSS.

rolling-letters-animation.module.css

.rolling-letters-animation {
  position: relative;
  display: inline-block;
  height: var(--letter-block-size);

  >.letters-wrapper {
    display: inline-block;
    width: var(--letter-block-size);
    height: var(--letter-block-size);
    overflow: hidden;

    >.letter {
      display: grid;
      place-items: center;
      width: var(--letter-block-size);
      height: var(--letter-block-size);
      overflow: hidden;

      &:first-child {
        animation: rolling-letters-keyframes var(--animation-duration) cubic-bezier(0, 0.34, 0.1, 1.11) forwards;
      }
    }

    &:nth-child(odd) {
      >.letter {
        &:first-child {
          margin-top: calc(var(--total-rolling-letters) * var(--letter-block-size) * -1);
          animation-name: rolling-letters-reverse-keyframes;
        }
      }
    }
  }

  > .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 rolling-letters-keyframes {
  from {
    margin-top: 0%;
  }

  to {
    margin-top: calc(var(--total-rolling-letters) * calc(var(--letter-block-size) * -1));
  }
}

@keyframes rolling-letters-reverse-keyframes {
  from {
    margin-top: calc(var(--total-rolling-letters) * calc(var(--letter-block-size) * -1));
  }

  to {
    margin-top: 0%;
  }
}

3. Use the component

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

rolling-letters-animation-preview.jsx

import RollingLettersAnimation from "@/components/text-effects/rolling-letters-animation/rolling-letters-animation";

const RollingLettersAnimationPreview = () => {
  return (
    <RollingLettersAnimation
      word="MosaicUI"
      letterBlockSize={32}
      style={{
        fontFamily: "monospace",
        fontSize: "32px",
        fontWeight: "800",
      }}
    />
  );
};

export default RollingLettersAnimationPreview;

Props

Configure the component with the following props:

PropTypeRequiredDefaultDescription
wordstringYesThe text to display and animate with the rolling letter effect.
letterBlockSizenumberNo16Size of the square block (width and height) used for each letter container. This helps maintain consistent spacing when fonts are not monospace. Typically set to match the text font-size.
durationnumberNo2000Total animation duration in milliseconds. Minimum value: 100.
classNamestringNoAdditional CSS class applied to the root container element.
styleReact.CSSPropertiesNoInline styles applied to the root container element.
letterClassNamestringNoCSS class applied to each individual letter element. Useful for custom letter styling or effects.
letterStyleReact.CSSPropertiesNoInline styles applied to each letter element.