Component

Typewriter

See on GitHub

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

pnpm dlx shadcn@latest add @joyco/typewriter

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 to texts.length > 1). Disabled when prefers-reduced-motion is 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 to msPerChar).
  • 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-motion is enabled, the full text is shown immediately, the caret is hidden, and autoplay is disabled.

Related Components

Maintainers
Downloads
6Total
0 downloads today