Installation
pnpm dlx shadcn@latest add https://asciicn.dev/r/typewriter-text.jsonUsage
import { TypewriterText } from "@/components/asciicn/typewriter-text";
export function Install() {
return (
<TypewriterText
as="p"
prompt="$ "
phrases={["pnpm add asciicn", "pnpm dev"]}
className="font-mono"
/>
);
}Characters land one at a time, speed per second on average. jitter makes the gaps uneven: a little longer after a space, longer still after punctuation, and now and then a short hesitation. Each finished phrase holds for pause seconds, then backspaces at backspaceSpeed and the next one starts. After the last phrase it starts over, unless loop is false.
The cursor is a glyph from the glyph font: █, _ or ▏. It stays solid while keys land and blinks in steps while it waits, as a terminal cursor does.
Examples
Rotating headline
One TypewriterText on the second line of a heading, cycling phrases behind an orange underscore cursor. The element takes the size of its longest phrase, so the heading does not reflow while it types.
Boot log
Three log lines with loop={false} and staggered delays, then a ready line. showCursor="typing" shows each log line's cursor only while it types. The ready line uses "started", so its cursor appears with its first key and keeps blinking.
Props
| Prop | Type | Default |
|---|---|---|
| phrases The lines to type, in order. Screen readers get all of them. | Type: readonly string[] | Default – |
| as The element to render. | Type: "h1" | "h2" | "h3" | "h4" | "p" | "span" | "div" | Default "span" |
| speed Characters typed per second, on average. | Type: number | Default 16 |
| backspaceSpeed Characters erased per second when backspacing. | Type: number | Default 40 |
| jitter How uneven the typing is, from 0 (even) to 1. | Type: number | Default 0.6 |
| pause Seconds to hold a typed phrase before erasing it. | Type: number | Default 1.8 |
| delay Seconds to wait before the first key. | Type: number | Default 0.2 |
| backspace Erase phrases one character at a time. false clears the line at once. | Type: boolean | Default true |
| loop Start over after the last phrase. false stops on the last phrase. | Type: boolean | Default true |
| cursor Cursor shape: █, _ or ▏. | Type: "block" | "underscore" | "bar" | Default "block" |
| showCursor When the cursor shows: always, from the first key on, or only while keys land. | Type: "always" | "started" | "typing" | Default "always" |
| prompt Text before the typed line that is never erased, such as "$ ". | Type: string | Default – |
| cursorColor Cursor colour. | Type: AsciiColor | Default "ink" |
| promptColor Prompt colour. | Type: AsciiColor | Default "grey" |
Other props pass through to the element.
Notes
- Every phrase is laid out invisibly in the same grid cell as the typed line, so the element always takes the size of its longest phrase. Typing and erasing never move the text around it.
- Screen readers get every phrase once, from a visually hidden copy. The typed line is
aria-hidden, so a screen reader does not read each keystroke. - With reduced motion nothing types and the cursor does not blink. The text shows in full: the first phrase when
loopis on, the last phrase when it is off. - Typing stops while the text is offscreen or the tab is hidden, and carries on from the same character when it comes back.
- Changing
phrasesstarts over from the first phrase.