Typewriter Effect for React

Typewriter effect that cycles through phrases with variable typing speed and blinking cursor. Pauses when out of viewport.

Component

Installation

 

Usage

Import

Add the Typewriter import.

import { Typewriter } from "@/components/typewriter";

Use

Use with phrases array.

<Typewriter phrases={["Phrase 1", "Phrase 2"]} />;

Guidelines

  • trigger='mount': starts immediately on mount. trigger='inView' (default): only animates when in viewport, pauses when scrolled away.
  • Pass an array of phrases. The component types each, pauses, deletes, then moves to the next.
  • Adjust typeSpeed, deleteSpeed, and pauseDuration for different feels.
  • Use cursorBlinkDuration and cursorBlinkEasing for cursor animation.

Props

All props are optional unless marked required. Use these to customize every aspect of the component.

PropTypeDefaultDescription
phrasesreadonly string[]-Array of phrases to cycle through.
classNamestring-Additional CSS classes for the container.
trigger"mount" | "inView""inView""mount" = start on mount. "inView" = only animate when in viewport, pause when scrolled away.
cursorClassNamestring-Classes for the blinking cursor (e.g. w-1, bg-amber-500).
typeSpeednumber100Base delay between characters in ms.
deleteSpeednumber50Delay when deleting in ms.
pauseDurationnumber2000Pause at end of phrase before deleting in ms.
cursorBlinkDurationnumber0.5Cursor blink cycle duration in seconds.
cursorBlinkEasingstring | number[]"easeInOut"Cursor blink easing: easeInOut, easeIn, easeOut, or cubic-bezier [0.4,0,0.2,1].
inViewThresholdnumber0.3IntersectionObserver threshold (0–1) for pausing when out of view.

Accessibility

  • Decorative text effect with no interactive controls, keyboard handlers, or focusable elements, so there is nothing for keyboard or focus management to operate on.
  • The blinking cursor is correctly marked aria-hidden so assistive technology ignores the visual caret.
  • The animated text renders as plain text content inside a span with no aria-live region, so screen readers may announce partial or rapidly changing strings rather than the final phrase.
  • The component does not honour prefers-reduced-motion, so the continuous typing, deleting, and cursor blink should be guarded with a matchMedia('(prefers-reduced-motion: reduce)') check to render static text for users who prefer reduced motion.

Performance

  • The cursor blink animates only opacity via framer-motion, which is compositor-friendly and avoids layout or paint work.
  • The typing and deleting effect mutates React state on each character and re-renders the span text, so it changes DOM text content rather than animating a cheap transform property.
  • Each step is driven by a single setTimeout that is cleared in the effect cleanup, and the cursor loops with repeat Infinity, so timers are tidied up on unmount or dependency change.
  • An IntersectionObserver gates the animation when trigger is inView and pauses work while off screen, and the observer is disconnected on cleanup to avoid leaks.
  • There is no reduced-motion gating, so the timers and cursor animation keep running for users who would prefer no motion.

Examples

In view (default)

Animates when scrolled into view, pauses when out of view.

import { Typewriter } from "@/components/typewriter";

<Typewriter
  phrases={["Build fast.", "Ship faster.", "Sleep never."]}
  trigger="inView"
  className="font-bold text-2xl"
/>;

On mount

Starts animating on mount, no viewport check.

import { Typewriter } from "@/components/typewriter";

<Typewriter
  phrases={["Starts immediately"]}
  trigger="mount"
  className="font-bold text-2xl"
/>;

Custom timing

Custom typing and delete speeds.

import { Typewriter } from "@/components/typewriter";

<Typewriter
  phrases={["Developer", "Designer", "Creator"]}
  typeSpeed={80}
  deleteSpeed={40}
  pauseDuration={1500}
/>;

Cursor customization

Slower blink, custom cursor width and color.

import { Typewriter } from "@/components/typewriter";

<Typewriter
  phrases={["Hello World"]}
  cursorBlinkDuration={0.8}
  cursorBlinkEasing="easeInOut"
  cursorClassName="w-1 bg-amber-500"
/>;

Cursor & viewport

Custom cursor class and viewport threshold.

import { Typewriter } from "@/components/typewriter";

<Typewriter
  phrases={["Visible text"]}
  cursorClassName="w-1 bg-primary"
  inViewThreshold={0.5}
/>;

Last updated on Jun 19

Made with ❤️ by Pulkit &

© 2026 Pulkit. All rights reserved

Last updated: