Rolling Letters

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

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;

Installation

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

Terminal / Console

npx mosaicui-cli@latest text-effects/rolling-letters-animation

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

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.