Docs
Typewriter Hero

Typewriter Hero

A hero section component with dynamic typewriter effect, customizable animations, and multiple text variations.

Build

Create stunning user interfaces with modern components and beautiful animations.

Installation

Copy and paste the following code into your project.

components/ui/typewriter-hero.tsx

import * as React from "react";
import { cn } from "@/lib/utils";
 
export interface TypewriterHeroProps
  extends React.HTMLAttributes<HTMLDivElement> {
  title?: string;
  description?: string;
  words?: string[];
  typingSpeed?: number;
  deletingSpeed?: number;
  pauseDuration?: number;
  className?: string;
  titleClassName?: string;
  descriptionClassName?: string;
  typingClassName?: string;
  cursorClassName?: string;
  align?: "left" | "center" | "right";
}
 
export function TypewriterHero({
  title = "Welcome to",
  description = "A modern and beautiful UI library for React",
  words = ["Beautiful", "Modern", "Responsive", "Accessible"],
  typingSpeed = 100,
  deletingSpeed = 50,
  pauseDuration = 2000,
  className,
  titleClassName,
  descriptionClassName,
  typingClassName,
  cursorClassName,
  align = "center",
  ...props
}: TypewriterHeroProps) {
  const [currentText, setCurrentText] = React.useState("");
  const [currentIndex, setCurrentIndex] = React.useState(0);
  const [isDeleting, setIsDeleting] = React.useState(false);
  const [isWaiting, setIsWaiting] = React.useState(false);
 
  React.useEffect(() => {
    const timeout = setTimeout(
      () => {
        if (isWaiting) {
          setIsWaiting(false);
          setIsDeleting(true);
          return;
        }
 
        if (isDeleting) {
          if (currentText === "") {
            setIsDeleting(false);
            setCurrentIndex((prev) => (prev + 1) % words.length);
          } else {
            setCurrentText((prev) => prev.slice(0, -1));
          }
        } else {
          const targetWord = words[currentIndex];
          if (currentText === targetWord) {
            setIsWaiting(true);
          } else {
            setCurrentText((prev) => targetWord.slice(0, prev.length + 1));
          }
        }
      },
      isWaiting ? pauseDuration : isDeleting ? deletingSpeed : typingSpeed,
    );
 
    return () => clearTimeout(timeout);
  }, [
    currentText,
    currentIndex,
    isDeleting,
    isWaiting,
    words,
    typingSpeed,
    deletingSpeed,
    pauseDuration,
  ]);
 
  return (
    <div
      className={cn(
        "relative w-full overflow-hidden py-24",
        align === "center" && "text-center",
        align === "right" && "text-right",
        className,
      )}
      {...props}
    >
      <div className="relative mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
        {title && (
          <h1
            className={cn(
              "text-4xl font-extrabold tracking-tight sm:text-5xl lg:text-6xl",
              titleClassName,
            )}
          >
            {title}{" "}
            <span
              className={cn(
                "bg-linear-to-r from-indigo-500 via-purple-500 to-pink-500 bg-clip-text text-transparent",
                typingClassName,
              )}
            >
              {currentText}
              <span
                className={cn(
                  "ml-1 inline-block h-[1em] w-[2px] animate-text-blink",
                  cursorClassName,
                )}
                aria-hidden="true"
              />
            </span>
          </h1>
        )}
        {description && (
          <p
            className={cn(
              "mt-6 max-w-3xl text-xl text-gray-600 dark:text-gray-400",
              align === "center" && "mx-auto",
              align === "right" && "ml-auto",
              descriptionClassName,
            )}
          >
            {description}
          </p>
        )}
      </div>
    </div>
  );
}

Update your globals.css

Add the following animations to your globals.css:

@theme {
   --animate-text-blink: text-blink 1.2s infinite ease-in-out;
 
  @keyframes text-blink {
    0%, 75%, 100%: { opacity: 1 },
    75.1%, 95%: { opacity: 0 },
  },
}

Update the import paths to match your project setup.

import TypewriterHero from "@/components/ui/typewriter-hero";

Usage

import TypewriterHero from "@/components/ui/typewriter-hero";
 
export default function Hero() {
  return (
    <TypewriterHero
      title="Build"
      description="Create stunning user interfaces with modern components."
      words={["Faster", "Better", "Smarter", "Together"]}
      typingSpeed={80}
      deletingSpeed={40}
      pauseDuration={2000}
    />
  );
}

Examples

With Gradient Text

<TypewriterHero
  title="Create"
  description="Build beautiful interfaces with modern components."
  words={["Faster", "Better", "Together"]}
  typingClassName="bg-linear-to-r from-blue-600 via-indigo-600 to-purple-600 dark:from-blue-400 dark:via-indigo-400 dark:to-purple-400"
  cursorClassName="bg-linear-to-r from-blue-600 via-indigo-600 to-purple-600 dark:from-blue-400 dark:via-indigo-400 dark:to-purple-400"
/>

Custom Alignment

<TypewriterHero
  align="left"
  title="Design"
  description="Craft stunning user experiences that delight."
  words={["Beautifully", "Efficiently", "Perfectly"]}
/>

Custom Animation Timing

<TypewriterHero
  title="Develop"
  description="Write clean, maintainable code with confidence."
  words={["React", "Next.js", "TypeScript"]}
  typingSpeed={50} // Faster typing
  deletingSpeed={30} // Faster deleting
  pauseDuration={3000} // Longer pause
/>

Props

PropTypeDefaultDescription
titlestring"Welcome to"The main title text
descriptionstring-Secondary description text
wordsstring[][]Array of words to cycle through
typingSpeednumber100Speed of typing animation in milliseconds
deletingSpeednumber50Speed of deleting animation in milliseconds
pauseDurationnumber2000Duration to pause between words
align"left" | "center" | "right""center"Text alignment
classNamestring-Additional wrapper classes
titleClassNamestring-Additional title classes
descriptionClassNamestring-Additional description classes
typingClassNamestring-Additional typing text classes
cursorClassNamestring-Additional cursor classes

Customization

Cursor Style

The cursor uses Tailwind's animation utilities for a realistic blinking effect. You can customize its appearance using the cursorClassName prop:

<TypewriterHero
  cursorClassName="w-[3px] bg-blue-500" // Thicker, blue cursor
/>

Text Gradients

Apply beautiful gradient effects to the typing text:

<TypewriterHero typingClassName="bg-linear-to-r from-pink-500 via-red-500 to-yellow-500 bg-clip-text" />

Dark Mode

The component automatically supports dark mode. You can customize dark mode styles using the appropriate Tailwind dark mode classes:

<TypewriterHero
  titleClassName="text-gray-900 dark:text-white"
  descriptionClassName="text-gray-600 dark:text-gray-300"
/>