Typewriter Text
Text that types itself out character by character, then cycles to the next phrase.
- Category
- Text Effects
- Added
- 2026-08-22
- Deps
- none
- Updated
- 2026-09-01
Props
"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>
);
}
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.
Pairs well with
Matched on shared tags and category — the entries most likely to be used alongside this one.
Rotating Word Swap Headline
text effectsA headline where one word auto-cycles with a vertical odometer-style swap.
Weight Wave Text
text effectsPer-character variable font weight that swells around the pointer and drifts when idle.
Specular Text Sweep
text effectsA light band travelling inside the glyphs, clipped to the text so only ink lights up.