Typewriter

Text that types itself character-by-character with a blinking caret — the canonical chat / terminal / LLM-streaming effect, generalized.

the quick brown fox jumps over the lazy dog
Customize
Timing
40ms
0ms
Behavior

Installation

npx shadcn@latest add https://craftbits.dev/r/typewriter.json

Usage

import { Typewriter } from "@craft-bits/core";
 
<Typewriter text="hello, world" />

Stream whole tokens instead of characters:

<Typewriter
  tokens={["The", "model", "is", "thinking…"]}
  speed={120}
/>

Understanding the component

  1. Two stacked spans. A visually-hidden sr-only span carries the full target text so screen readers always announce the finished sentence — even mid-type. The visible reveal is aria-hidden.
  2. Tick-by-tick reveal. A single setTimeout chain advances the revealed index by one per tick. speed is the gap between ticks; startDelay defers the first tick; loop restarts from seedLength after a loopDelay pause.
  3. Tokens vs. characters. When tokens is supplied, each tick reveals one whole token (joined with spaces) instead of one character — handy for word-by-word LLM streams.
  4. Seed prefix. seedLength characters render instantly before the reveal begins. Useful when the prefix is already known — e.g. a chat prompt the user typed manually.
  5. CSS-driven caret. The blinking cursor is a pure CSS @keyframes animation, so prefers-reduced-motion: reduce collapses it to a steady caret automatically — no JS branching.

Props

PropTypeDefaultDescription
textstringrequiredThe full text to type out.
speednumber30Milliseconds per character (or per token).
startDelaynumber0Delay before typing starts (ms).
seedLengthnumber0Number of leading characters rendered instantly.
loopbooleanfalseRestart from seedLength after finishing.
loopDelaynumber1200Pause between loop iterations (ms).
cursorbooleantrueShow the blinking caret.
cursorCharstring"|"The caret glyph.
cursorBlinkOnCompletebooleantrueKeep the caret visible after typing finishes.
cursorClassNamestringClass applied to the caret element.
tokensreadonly string[]Reveal whole tokens (joined with spaces) instead of characters.
onComplete() => voidCalled once when the reveal reaches the end.

Accessibility

  • The full target text is announced via a .sr-only span so screen readers don't follow the character-by-character reveal.
  • The blinking caret uses CSS @keyframes gated by @media (prefers-reduced-motion: reduce) — it stops blinking automatically when reduced motion is requested.
  • The typing animation itself is fully skipped under prefers-reduced-motion: reduce — the text renders instantly on first paint.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/viz/TypewriterViz.tsx). The original was tied to a Baby-GPT lesson; craft-bits ships only the core typewriter mechanic so it generalizes to chat, terminals, and LLM-streaming UIs.