Skip to main content

Overview

The type-writer-effect.tsx file exports two typewriter animation components:
  1. TypewriterEffect - Character-by-character reveal with staggered animation
  2. TypewriterEffectSmooth - Smooth width-based reveal animation
Both components feature an animated blinking cursor and support per-word styling.

Source Location

/workspace/source/src/app/components/ui/type-writer-effect.tsx

TypewriterEffect Component

Overview

Reveals text character by character with a staggered animation. Each character fades in and expands from hidden to visible when the component enters the viewport.

Visual Behavior

  • Characters start hidden and with 0 opacity
  • Animate to visible with 100ms stagger delay between each character
  • Uses useInView hook to trigger animation on scroll
  • Includes animated blinking cursor at the end

Props

words
Word[]
required
Array of word objects to animate. Each word can have custom styling.
type Word = {
  text: string;
  className?: string;
}
className
string
Additional classes for the outer container
cursorClassName
string
Additional classes for the cursor element

Type Definition

From /workspace/source/src/app/components/ui/type-writer-effect.tsx:7-18:
export const TypewriterEffect = ({
  words,
  className,
  cursorClassName,
}: {
  words: {
    text: string;
    className?: string;
  }[];
  className?: string;
  cursorClassName?: string;
})

Animation Details

From /workspace/source/src/app/components/ui/type-writer-effect.tsx:27-45:
const [scope, animate] = useAnimate();
const isInView = useInView(scope);

useEffect(() => {
  if (isInView) {
    animate(
      "span",
      {
        display: "inline-block",
        opacity: 1,
        width: "fit-content",
      },
      {
        duration: 0.3,
        delay: stagger(0.1),
        ease: "easeInOut",
      }
    );
  }
}, [isInView, animate]);
Key animation properties:
  • Duration: 0.3 seconds per character
  • Stagger delay: 0.1 seconds (100ms) between each character
  • Easing: easeInOut for smooth acceleration/deceleration
  • Trigger: Animation starts when component enters viewport

Usage Example

From /workspace/source/src/app/components/Navigation.tsx:12,48-54:
import { TypewriterEffect } from "./ui/type-writer-effect";
import Link from "next/link";

export default function Navigation() {
  return (
    <Link href="/" className="flex gap-2 text-3xl font-bold items-center">
      <FaCode className="text-spotify-green w-[70px]" />
      <TypewriterEffect
        words={[
          { text: "Luan", className: "text-4xl" },
          { text: "Nguyen", className: "text-4xl" },
        ]}
        className="place-self-center max-lg:hidden text-white"
      />
    </Link>
  );
}

Default Styling

Container

"text-base sm:text-xl md:text-3xl lg:text-5xl font-semibold text-center flex items-end"

Character Spans

"text-white opacity-0 hidden mb-2"

Cursor

"inline-block rounded-sm w-[4px] h-4 md:h-6 lg:h-10 bg-blue-500 mb-2"

Cursor Animation

From /workspace/source/src/app/components/ui/type-writer-effect.tsx:80-96:
<motion.span
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  transition={{
    duration: 0.8,
    repeat: Infinity,
    repeatType: "reverse",
  }}
  className={cn(
    "inline-block rounded-sm w-[4px] h-4 md:h-6 lg:h-10 bg-blue-500 mb-2",
    cursorClassName
  )}
/>
The cursor blinks with a 0.8-second duration, creating a classic typewriter effect.

TypewriterEffectSmooth Component

Overview

Creates a smooth reveal animation by animating the width of a container from 0% to fit-content. All text appears together rather than character-by-character.

Visual Behavior

  • Text starts with 0% width (hidden)
  • Animates to full width over 2 seconds
  • Uses whileInView to trigger on scroll
  • Includes 1-second delay before animation starts
  • Features the same blinking cursor effect

Props

words
Word[]
required
Array of word objects to display. Same format as TypewriterEffect.
className
string
Additional classes for the outer container
cursorClassName
string
Additional classes for the cursor element

Type Definition

From /workspace/source/src/app/components/ui/type-writer-effect.tsx:101-112:
export const TypewriterEffectSmooth = ({
  words,
  className,
  cursorClassName,
}: {
  words: {
    text: string;
    className?: string;
  }[];
  className?: string;
  cursorClassName?: string;
})

Animation Details

From /workspace/source/src/app/components/ui/type-writer-effect.tsx:147-160:
<motion.div
  className="overflow-hidden pb-2"
  initial={{ width: "0%" }}
  whileInView={{ width: "fit-content" }}
  transition={{
    duration: 2,
    ease: "linear",
    delay: 1,
  }}
>
  <div className="text-xs sm:text-base md:text-xl lg:text:3xl xl:text-5xl font-bold"
       style={{ whiteSpace: "nowrap" }}>
    {renderWords()}
  </div>
</motion.div>
Key animation properties:
  • Duration: 2 seconds total
  • Delay: 1 second before animation starts
  • Easing: Linear for consistent speed
  • White-space: nowrap prevents text wrapping during animation

Usage Example

import { TypewriterEffectSmooth } from "@/components/ui/type-writer-effect";

export default function Hero() {
  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <TypewriterEffectSmooth
        words={[
          { text: "Build" },
          { text: "awesome" },
          { text: "apps", className: "text-blue-500" },
          { text: "with" },
          { text: "Next.js", className: "text-blue-500" },
        ]}
      />
    </div>
  );
}

Default Styling

Container

"flex space-x-1 my-6"

Text Wrapper

"text-xs sm:text-base md:text-xl lg:text:3xl xl:text-5xl font-bold"

Cursor

"block rounded-sm w-[4px] h-4 sm:h-6 xl:h-12 bg-blue-500"

Comparison: TypewriterEffect vs TypewriterEffectSmooth

FeatureTypewriterEffectTypewriterEffectSmooth
Animation StyleCharacter-by-character staggerSmooth width expansion
Duration0.3s per character + stagger2s total
Initial DelayNone1 second
TriggeruseInViewwhileInView
Text Alignmentitems-endspace-x-1
Best ForShort text, dramatic revealsLonger text, smooth reveals

Customization Examples

Multi-colored Text

<TypewriterEffect
  words={[
    { text: "Hello", className: "text-red-500" },
    { text: "World", className: "text-blue-500" },
  ]}
/>

Custom Cursor

<TypewriterEffectSmooth
  words={words}
  cursorClassName="bg-green-500 w-1 h-8"
/>

Faster Animation

// Modify the component source to adjust duration and delay values
// TypewriterEffect: Change duration from 0.3 to 0.1
// TypewriterEffectSmooth: Change duration from 2 to 1

Implementation Notes

Character Splitting

Both components split words into character arrays: From /workspace/source/src/app/components/ui/type-writer-effect.tsx:20-25:
const wordsArray = words.map((word) => {
  return {
    ...word,
    text: word.text.split(""),
  };
});

Spacing Between Words

A non-breaking space (&nbsp;) is added after each word:
<div key={`word-${idx}`} className="inline-block">
  {/* character spans */}
  &nbsp;
</div>

Dependencies

  • framer-motion - Required hooks:
    • motion - For animated elements
    • stagger - For sequential character animation
    • useAnimate - For programmatic animations
    • useInView - For viewport detection
  • @/lib/utils - Provides cn() utility
  • React (useEffect)

Accessibility Considerations

Typewriter animations can be distracting for users with motion sensitivity. Consider respecting prefers-reduced-motion:
const prefersReducedMotion = window.matchMedia(
  "(prefers-reduced-motion: reduce)"
).matches;

// Skip animation if user prefers reduced motion
if (!prefersReducedMotion) {
  animate(/* ... */);
}

Performance Tips

  • Keep text length reasonable (< 50 characters) for character-by-character animation
  • Use TypewriterEffectSmooth for longer text passages
  • The useInView hook prevents animation until needed, improving initial page load

Common Use Cases

  1. Hero Headlines - Large, attention-grabbing titles
  2. Navigation Branding - Animated logo text (as seen in the example)
  3. Section Introductions - Reveal section titles on scroll
  4. Call-to-Action - Emphasize important messages
  5. Loading States - Creative alternative to spinners

Build docs developers (and LLMs) love