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.jsonUsage
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
- Two stacked spans. A visually-hidden
sr-onlyspan carries the full target text so screen readers always announce the finished sentence — even mid-type. The visible reveal isaria-hidden. - Tick-by-tick reveal. A single
setTimeoutchain advances the revealed index by one per tick.speedis the gap between ticks;startDelaydefers the first tick;looprestarts fromseedLengthafter aloopDelaypause. - Tokens vs. characters. When
tokensis supplied, each tick reveals one whole token (joined with spaces) instead of one character — handy for word-by-word LLM streams. - Seed prefix.
seedLengthcharacters render instantly before the reveal begins. Useful when the prefix is already known — e.g. a chat prompt the user typed manually. - CSS-driven caret. The blinking cursor is a pure CSS
@keyframesanimation, soprefers-reduced-motion: reducecollapses it to a steady caret automatically — no JS branching.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
text | string | required | The full text to type out. |
speed | number | 30 | Milliseconds per character (or per token). |
startDelay | number | 0 | Delay before typing starts (ms). |
seedLength | number | 0 | Number of leading characters rendered instantly. |
loop | boolean | false | Restart from seedLength after finishing. |
loopDelay | number | 1200 | Pause between loop iterations (ms). |
cursor | boolean | true | Show the blinking caret. |
cursorChar | string | "|" | The caret glyph. |
cursorBlinkOnComplete | boolean | true | Keep the caret visible after typing finishes. |
cursorClassName | string | — | Class applied to the caret element. |
tokens | readonly string[] | — | Reveal whole tokens (joined with spaces) instead of characters. |
onComplete | () => void | — | Called once when the reveal reaches the end. |
Accessibility
- The full target text is announced via a
.sr-onlyspan so screen readers don't follow the character-by-character reveal. - The blinking caret uses CSS
@keyframesgated 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.