Skip to Content
⭐️ Leave a star →
ComponentsTypewriter

Typewriter

Reveals text one token at a time at a steady cadence. Streaming-aware — grow the text prop as tokens arrive and the reveal continues from where it left off. Compose with Typewriter.Text and Typewriter.Cursor, or drive your own UI with the render-prop state.

Features

Reveals character-by-character or word-by-word via mode.
Streaming-aware — append to text and the reveal continues from where it left off.
Optional blinking cursor that unmounts when typing finishes (or stays via keepMounted).
Render-prop exposes displayed, isTyping, isDone, and progress.
Loop, start delay, and configurable speed.
Respects prefers-reduced-motion — reveals everything at once.

Example

AI

Assistant

import { Typewriter } from '@wire-ui/react' <Typewriter.Root text="Composing a reply…" speed={45}> <Typewriter.Text /> <Typewriter.Cursor keepMounted>▋</Typewriter.Cursor> </Typewriter.Root>

Render-prop state

Pass a function as the child of Typewriter.Root to drive your own UI from the reveal state instead of using Typewriter.Text/Typewriter.Cursor.

<Typewriter.Root text="Drive your own UI from the reveal state." speed={35}> {({ displayed, progress, isDone }) => ( <div> <p className="font-mono text-sm text-black">{displayed}</p> <div className="h-1 w-full overflow-hidden rounded-full bg-[#e5e7eb]"> <div className="h-full bg-black" style={{ width: `${progress * 100}%` }} /> </div> <p className="text-xs text-[#6b7280]">{isDone ? 'Done' : `${Math.round(progress * 100)}%`}</p> </div> )} </Typewriter.Root>

When no children are provided, Typewriter.Root renders the revealed text directly.

Streaming

Leave resetOnTextChange at its default (false) and grow the text prop incrementally as tokens arrive. The reveal continues from where it left off rather than restarting, smoothing out a bursty network stream.

const [text, setText] = useState('') // append chunks as they stream in: setText((t) => t + chunk) <Typewriter.Root text={text} speed={20}> <Typewriter.Text /> <Typewriter.Cursor>▋</Typewriter.Cursor> </Typewriter.Root>

Styling

Both Typewriter.Text and Typewriter.Cursor (and Root) expose data-state with the value "typing" or "done". Use it to fade or hide the cursor when typing finishes, or to style the text while in progress.

<Typewriter.Cursor className=" data-[state=typing]:opacity-100 data-[state=done]:opacity-0 "> </Typewriter.Cursor>

A blinking cursor is purely CSS:

@keyframes blink { 0%, 49% { opacity: 1 } 50%, 100% { opacity: 0 } } .cursor { animation: blink 1s step-end infinite }

Using data attributes

/* Style text while typing */ [data-state="typing"] { /* in progress */ } [data-state="done"] { /* finished */ }

Root props

PropTypeDefaultDescription
textstringrequiredFull text to reveal. Grow it over time for streaming.
speednumber30Milliseconds per revealed token (char or word).
mode'char' | 'word''char'Reveal one character or one whole word per tick.
startDelaynumber0Delay in ms before the first token is revealed.
autoStartbooleantrueBegin revealing automatically on mount.
resetOnTextChangebooleanfalseRestart from the beginning when text changes instead of continuing.
loopbooleanfalseClear and retype once complete.
loopDelaynumber1000Ms to wait before looping when loop is set.
onComplete() => voidCalled once all text has been revealed.
childrenReactNode | ((state: TypewriterState) => ReactNode)Compose with Text/Cursor, or a render function. Omitted renders the revealed text directly.

Text props

Typewriter.Text renders the revealed portion of text. It accepts standard element attributes (e.g. className).

Cursor props

PropTypeDefaultDescription
keepMountedbooleanfalseKeep the cursor mounted after typing finishes. When false, it unmounts once isDone is true.

Render-prop state (TypewriterState)

FieldTypeDescription
displayedstringThe portion of text currently revealed.
isTypingbooleanTrue while tokens are still being revealed.
isDonebooleanTrue once all of text has been revealed.
progressnumberFraction revealed, 0–1.

Data attributes

AttributeElementValues
data-stateRoot, Text, Cursor"typing" / "done"

Accessibility

  • Typewriter.Root sets aria-busy while typing is in progress.
  • Typewriter.Cursor is rendered with aria-hidden="true" so it is not announced.
  • Respects prefers-reduced-motion: when set, all text is revealed immediately rather than animated.
Last updated on

MIT License © 2026 wire-ui

Typewriter – Wire UI