Typewriter
A typewriter text component powered by the Web Animations API (type → hold → delete → next).
npx shadcn@latest add
'use client'
import { Typewriter } from '@/components/typewriter'
export default function TypewriterDemo() {
return (
<div className="flex min-h-[120px] items-center justify-center px-6 py-12">
<span className="text-foreground min-w-0 font-mono text-sm leading-6 whitespace-pre">
npx shadcn@latest add{' '}
<Typewriter
msPerChar={55}
pauseMs={650}
deleteMsPerChar={40}
gapMs={180}
texts={['joyco/mobile-menu', 'joyco/typewriter', 'joyco/chat']}
className='**:data-[slot="caret"]:[animation:pulse_0.75s_ease-in-out_infinite] **:data-[slot="caret"]:opacity-0'
/>
</span>
</div>
)
}
Installation
Usage
import { Typewriter } from '@/components/typewriter'
<p>
Build{' '}
<Typewriter
texts={['faster', 'safer', 'smarter']}
msPerChar={60} // typing speed
pauseMs={900} // hold time (fully typed)
startHoldMs={1200} // initial hold for the first word (only first cycle)
deleteMsPerChar={45} // delete speed
gapMs={150} // delay before next word
/>
</p>Props
- texts:
string[]— Phrases to type. When more than one is provided, the component can cycle. - autoPlay:
boolean— Automatically cycle phrases (defaults totexts.length > 1). Disabled whenprefers-reduced-motionis enabled. - index / defaultIndex / onIndexChange: Control the active phrase (controlled/uncontrolled).
- msPerChar:
number— Typing speed per character. - pauseMs:
number— Pause after typing completes before advancing. - startHoldMs:
number— Initial hold for the first word (index 0) before deleting starts (only used on the first cycle). - deleteMsPerChar:
number— Delete speed per character (defaults tomsPerChar). - gapMs:
number— Pause after deleting completes before starting the next word. - loop:
boolean— Loop back to the first phrase after the last. - caret:
boolean— Show/hide the blinking caret. - ariaLive:
"off" | "polite" | "assertive"— Screen reader announcement mode (defaults to"off").
Caret Styling
The caret is static by default. Use the data-slot="caret" selector to style it:
<Typewriter
texts={['hello', 'world']}
className='**:data-[slot="caret"]:animate-pulse'
/>Accessibility
- ARIA live region: Controlled via
ariaLive. Default is"off"to avoid noisy announcements while cycling. - Reduced motion: When
prefers-reduced-motionis enabled, the full text is shown immediately, the caret is hidden, and autoplay is disabled.