Typewriter

Types and deletes phrases with a blinking caret.

Ask anything

Installation

  1. Install the dependencies

    $ npm i motion lucide-react
  2. Copy the source into components/ui/typewriter.tsx

    components/ui/typewriter.tsx
    "use client";
    
    import { motion } from "motion/react";
    import { useEffect, useState } from "react";
    
    type TypewriterProps = {
      phrases: string[];
      typingSpeed?: number;
      deletingSpeed?: number;
      /** Milliseconds to hold a finished phrase */
      pause?: number;
      className?: string;
    };
    
    export function Typewriter({
      phrases,
      typingSpeed = 55,
      deletingSpeed = 30,
      pause = 1500,
      className = "",
    }: TypewriterProps) {
      const [phraseIndex, setPhraseIndex] = useState(0);
      const [text, setText] = useState("");
      const [deleting, setDeleting] = useState(false);
    
      useEffect(() => {
        const full = phrases[phraseIndex % phrases.length];
        let delay = deleting ? deletingSpeed : typingSpeed;
        if (!deleting && text === full) delay = pause;
    
        const timer = setTimeout(() => {
          if (!deleting && text === full) return setDeleting(true);
          if (deleting && text === "") {
            setDeleting(false);
            return setPhraseIndex((i) => i + 1);
          }
          setText(deleting ? full.slice(0, text.length - 1) : full.slice(0, text.length + 1));
        }, delay);
    
        return () => clearTimeout(timer);
      }, [text, deleting, phraseIndex, phrases, typingSpeed, deletingSpeed, pause]);
    
      return (
        <span className={className}>
          {text}
          <motion.span
            aria-hidden
            className="ml-0.5 inline-block h-[1em] w-[2px] translate-y-[0.12em] bg-current"
            animate={{ opacity: [1, 0, 1] }}
            transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
          />
        </span>
      );
    }
    
    export default function Demo() {
      return (
        <div className="text-center">
          <p className="text-sm font-medium uppercase tracking-widest text-zinc-400">Ask anything</p>
          <p className="mt-3 text-3xl font-semibold tracking-tight text-zinc-900 sm:text-4xl">
            <Typewriter
              phrases={["Summarize this quarter's revenue", "Draft a launch announcement", "Find churn risks in Q3"]}
            />
          </p>
        </div>
      );
    }
    
  3. Use it

    The file exports the component and a Demo showing typical usage. Import whichever you need.