⌨️ Typewriter text

A captivating typewriter effect component that animates text with realistic typing and deleting animations. Perfect for hero sections, landing pages, and dynamic content displays!

Life is about |

Install dependencies

npm i clsx tailwind-merge motion

Add utility file

lib/utils.ts

import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Code

Typewriter.tsx

"use client"

import { useState, useEffect } from "react"
import { cn } from "@/lib/utils"

interface TypewriterProps {
  text: string[]
  speed?: number
  deleteSpeed?: number
  waitTime?: number
  className?: string
  cursorChar?: string
  showCursor?: boolean
  loop?: boolean
}

export default function Typewriter({
  text,
  speed = 100,
  deleteSpeed = 50,
  waitTime = 2000,
  className = "",
  cursorChar = "|",
  showCursor = true,
  loop = true,
}: TypewriterProps) {
  const [currentText, setCurrentText] = useState("")
  const [currentIndex, setCurrentIndex] = useState(0)
  const [isDeleting, setIsDeleting] = useState(false)
  const [showCursorBlink, setShowCursorBlink] = useState(true)

  useEffect(() => {
    const timeout = setTimeout(
      () => {
        const current = text[currentIndex]

        if (isDeleting) {
          setCurrentText(current.substring(0, currentText.length - 1))
        } else {
          setCurrentText(current.substring(0, currentText.length + 1))
        }

        if (!isDeleting && currentText === current) {
          setTimeout(() => setIsDeleting(true), waitTime)
        } else if (isDeleting && currentText === "") {
          setIsDeleting(false)
          setCurrentIndex((prevIndex) => {
            if (!loop && prevIndex === text.length - 1) {
              return prevIndex
            }
            return (prevIndex + 1) % text.length
          })
        }
      },
      isDeleting ? deleteSpeed : speed,
    )

    return () => clearTimeout(timeout)
  }, [currentText, currentIndex, isDeleting, text, speed, deleteSpeed, waitTime, loop])

  useEffect(() => {
    const cursorInterval = setInterval(() => {
      setShowCursorBlink((prev) => !prev)
    }, 500)

    return () => clearInterval(cursorInterval)
  }, [])

  return (
    <span className={cn("inline-block", className)}>
      {currentText}
      {showCursor && <span className={cn("ml-1", showCursorBlink ? "opacity-100" : "opacity-0")}>{cursorChar}</span>}
    </span>
  )
}

Props

PropTypeDefaultDescription
textstring[]-Array of strings to cycle through
speednumber100Typing speed in milliseconds
deleteSpeednumber50Deleting speed in milliseconds
waitTimenumber2000Wait time before deleting in milliseconds
classNamestring""Additional CSS classes
cursorCharstring"|"Character to use as cursor
showCursorbooleantrueWhether to show the blinking cursor
loopbooleantrueWhether to loop through text array
Typewritter16