Oblivion
Oblivion
Docs
Go back
Component by
OV
Oblivion
TSX
typing-effect
"use client"

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

export const componentMeta = {
  name: "Typing Effect",
  description: "A typewriter component that cycles through an array of words with a blinking cursor.",
  props: {
    words:     { type: "string[]", default: '["React", "Next.js", "Tailwind"]', description: "Words to cycle through" },
    speed:     { type: "number",   default: "80",  description: "Typing speed in ms per character" },
    deleteSpeed: { type: "number", default: "40",  description: "Deletion speed in ms per character" },
    pause:     { type: "number",   default: "1800", description: "Pause in ms after a word is fully typed" },
    className: { type: "string",   description: "Extra classes for the text" },
  }
}

type Phase = "typing" | "pausing" | "deleting"

export default function TypingEffect({
  words        = ["React", "Next.js", "Tailwind CSS", "Framer Motion"],
  speed        = 80,
  deleteSpeed  = 40,
  pause        = 1800,
  className,
}: {
  words?:       string[]
  speed?:       number
  deleteSpeed?: number
  pause?:       number
  className?:   string
}) {
  const [display, setDisplay] = useState("")
  const [phase,   setPhase]   = useState<Phase>("typing")
  const wordIdx = useRef(0)
  const charIdx = useRef(0)

  useEffect(() => {
    const current = words[wordIdx.current % words.length]

    if (phase === "typing") {
      if (charIdx.current < current.length) {
        const t = setTimeout(() => {
          setDisplay(current.slice(0, charIdx.current + 1))
          charIdx.current++
        }, speed)
        return () => clearTimeout(t)
      } else {
        const t = setTimeout(() => setPhase("pausing"), pause)
        return () => clearTimeout(t)
      }
    }

    if (phase === "pausing") {
      const t = setTimeout(() => setPhase("deleting"), 100)
      return () => clearTimeout(t)
    }

    if (phase === "deleting") {
      if (charIdx.current > 0) {
        const t = setTimeout(() => {
          charIdx.current--
          setDisplay(current.slice(0, charIdx.current))
        }, deleteSpeed)
        return () => clearTimeout(t)
      } else {
        wordIdx.current++
        setPhase("typing")
      }
    }
  }, [display, phase, words, speed, deleteSpeed, pause])

  return (
    <span className={cn("inline-flex items-center", className)}>
      <span>{display}</span>
      {/* Blinking cursor */}
      <span
        className="ml-[2px] inline-block w-[2px] h-[1.1em] rounded-sm bg-current animate-[ob-blink_1s_step-end_infinite]"
        aria-hidden
      />
      <style dangerouslySetInnerHTML={{ __html: `
        @keyframes ob-blink { 0%,100%{opacity:1} 50%{opacity:0} }
      `}} />
    </span>
  )
}

export function TypingEffectPreview() {
  return (
    <div className="flex w-full items-center justify-center bg-transparent px-8 py-14">
      <div className="text-center space-y-4">
        <p className="text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400">
          Oblivion UI
        </p>
        <h1 className="text-4xl md:text-5xl font-bold tracking-tight text-zinc-900 dark:text-white">
          Built for{" "}
          <TypingEffect
            words={["React", "Next.js", "Tailwind", "Framer Motion"]}
            className="text-indigo-600 dark:text-indigo-400"
          />
        </h1>
        <p className="text-zinc-500 dark:text-zinc-400 max-w-sm mx-auto text-sm">
          A typewriter that cycles smoothly through your key phrases.
        </p>
      </div>
    </div>
  )
}
Save to favoritesCopy to Figma
React

Typing Effect

A typewriter component that cycles through an array of words with a blinking cursor.

Installation

$ npx @cosmoo/oblivion add typing-effect
Dependenciesnpm install

Usage Example

tsx
import TypingEffect from "@/components/oblivion/typing-effect"

export default function Demo() {
  return (
    <div className="p-10 flex justify-center">
      <TypingEffect />
    </div>
  )
}