Skip to content

Typewriter Text

Text that types itself out character by character, then cycles to the next phrase.

Freeeditorialminimal
Category
Text Effects
Added
2026-08-22
Deps
none
Updated
2026-09-01
Preview

Props

60
1400
No runtime dependencies
"use client";

import { useEffect, useState } from "react";

export function TypewriterText({
  phrases = ["Build fast.", "Ship faster.", "Vibe code."],
  typeSpeed = 60,
  pauseMs = 1400,
}: {
  phrases?: string[];
  typeSpeed?: number;
  pauseMs?: number;
}) {
  const [phraseIndex, setPhraseIndex] = useState(0);
  const [text, setText] = useState("");
  const [mode, setMode] = useState<"typing" | "pausing" | "erasing">("typing");

  useEffect(() => {
    const current = phrases[phraseIndex % phrases.length] ?? "";

    if (mode === "typing") {
      if (text.length < current.length) {
        const t = setTimeout(() => setText(current.slice(0, text.length + 1)), typeSpeed);
        return () => clearTimeout(t);
      }
      const t = setTimeout(() => setMode("pausing"), pauseMs);
      return () => clearTimeout(t);
    }

    if (mode === "pausing") {
      const t = setTimeout(() => setMode("erasing"), 0);
      return () => clearTimeout(t);
    }

    // erasing
    if (text.length > 0) {
      const t = setTimeout(() => setText(current.slice(0, text.length - 1)), typeSpeed / 2);
      return () => clearTimeout(t);
    }
    const t = setTimeout(() => {
      setPhraseIndex((i) => (i + 1) % phrases.length);
      setMode("typing");
    }, 200);
    return () => clearTimeout(t);
  }, [text, mode, phraseIndex, phrases, typeSpeed, pauseMs]);

  return (
    <span className="text-3xl font-black tracking-tight">
      {text}
      <span className="ml-0.5 inline-block w-[2px] translate-y-[2px] animate-pulse bg-red-500 align-baseline" style={{ height: "1em" }} />
    </span>
  );
}
Notes

About this text effect

A typing-effect headline built with a CSS steps animation can type once and stop; cycling through multiple phrases with independent type and erase speeds needs real state. This component runs a three-mode state machine — typing, pausing, erasing — from a single effect that schedules exactly one timeout per render based on the current mode, rather than an interval or an animation-frame loop; the effect re-fires because it's keyed on the values it just changed, which is what makes the timing self-correcting without a separate loop. Reach for this over a static rotating-words swap when the type-out motion itself is the point, not just the word change. The one non-obvious tuning choice: erasing runs at half the typing delay, deliberately asymmetric, so a full cycle doesn't feel like a slow metronome. The caret blinks via pure CSS, no timer involved. Its real limitation is readability, not correctness: the visible text is a fragment of a word for most of the animation, a genuinely rough experience for anyone relying on a screen reader or who finds constantly mutating text hard to track.

Curator’s note

One effect, one setTimeout per render, three modes — no interval, no rAF loop for the typing itself. Erasing runs at half the typing speed on purpose, which is what makes the cycle read as snappy instead of symmetric and slow.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

A headline where one word auto-cycles with a vertical odometer-style swap.

textheadlinecycle
Featured
New

Weight Wave Text

text effects

Per-character variable font weight that swells around the pointer and drifts when idle.

textvariable-fonthover
Featured
NewPro

A light band travelling inside the glyphs, clipped to the text so only ink lights up.

textgradientshine