Text Emerge

Animates text by gradually turning blurry letters into clear ones, word or letter by letter.

1. Copy the component file

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

text-emerge-animation.jsx

import { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import styles from "./text-emerge-animation.module.css";

const TextEmergeUnit = (props) => {
  const {
    animationDurationMs,
    children
  } = props;

  const ref = useRef();

  useEffect(() => {
    const timeoutId = setTimeout(() => {
      ref.current?.classList.add(styles["visible"]);
    }, animationDurationMs);
    return () => {
      clearTimeout(timeoutId);
    };
  }, [animationDurationMs]);

  return (
    <span
      aria-hidden={true}
      ref={ref}
    >
      {children}
    </span>
  )
};

const TextEmergeAnimation = (props) => {
  const {
    text,
    type = "word",
    speed = 100
  } = props;

  const animationDurationMs = 800;

  const [currentIndex, setCurrentIndex] = useState(-1);

  const textMapping = useMemo(() => (
    text
    .split(" ")
    .filter(Boolean)
    .map(word => {
      if (type === "word") {
        return [word, " "];
      } else {
        return [
          ...word
            .split("")
            .filter(Boolean),
          " "
        ];
      }
    })
    .flat()
  ), [text, type]);

  useLayoutEffect(() => {
    const timeout = setTimeout(() => {
      const isLast = currentIndex === (textMapping.length - 1);
      if (isLast) return;
      setCurrentIndex(currentIndex + 1);
    }, speed);
    return () => {
      clearTimeout(timeout);
    }
  }, [currentIndex, textMapping, speed]);

  return (
    <span 
      aria-label={text}
      className={styles["text-emerge-animation"]}
      style={{
        "--animation-duration": `${animationDurationMs}ms`
      }}
    >
      {textMapping.map((entry, entryIndex) => (
        entryIndex <= currentIndex ? (
          <TextEmergeUnit
            key={`text-${entryIndex}`}
            animationDurationMs={animationDurationMs}
          >
            {entry}
          </TextEmergeUnit>
        ) : null
      ))}
    </span>
  )
};

export default memo(TextEmergeAnimation);

2. Copy the CSS module file

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

text-emerge-animation.module.css

.text-emerge-animation {
  --animation-duration: 0;
  > span {
    opacity: 0;
    filter: blur(20px);
    animation: text-emerge-keyframes var(--animation-duration) ease-in-out forwards;

    &.visible {
      opacity: 1;
      filter: blur(0px);
      animation: none;
    }
  }
}

@keyframes text-emerge-keyframes {
  0% {
    opacity: 0;
    filter: blur(20px);
  }
  100% {
    opacity: 1;
    filter: blur(0px);
  }
}

3. Use the component

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

text-emerge-animation-preview.jsx

import TextEmergeAnimation from "@/components/text-effects/text-emerge-animation/text-emerge-animation";

const text = (`
A subtle motion to guide your attention. Nothing loud, nothing distracting — just a quiet transition that makes the interface feel alive. 
Good animation isn't decoration; it's a gentle cue that helps you understand where you are and what happens next.
`
);


const TextEmergeAnimationPreview = () => {
  return (
    <div 
      style={{
        width: "calc(100% - 64px)",
        height: "calc(100% - 64px)",
      }}
    >
      <h2>
        <TextEmergeAnimation 
          text={text}
          speed={50}
        />
      </h2>
    </div>
  );
};

export default TextEmergeAnimationPreview;

Props

Configure the component with the following props:

PropTypeRequiredDefaultDescription
textstringYesThe text to display. Can be a word, sentence, or paragraph depending on type.
type"word" | "letter"No"word"Determines the animation unit: "word" animates one word at a time, "letter" animates each letter individually.
speednumberNo100Typing speed in milliseconds per word or letter, depending on the type.