Claude
Skills
Sign in
Back

text-animations

Included with Lifetime
$97 forever

Expert knowledge for creative text animations - typewriter effects, character-by-character reveals, kinetic typography, split text animations, and expressive text motion.

General

What this skill does


# Text Animations Skill

Expert knowledge for creative text animations - typewriter effects, character-by-character reveals, kinetic typography, split text animations, and expressive text motion.

## When to Use

Activate this skill when:
- User wants typewriter/typing animations
- Creating character-by-character text reveals
- Building kinetic typography effects
- Need animated headings or titles
- Creating text scramble effects
- Building split-text animations

## File Patterns

- `**/*.tsx` with text animation components
- `**/components/Text*.tsx`
- `**/components/Heading*.tsx`
- `**/components/Title*.tsx`

## Text Animation Types

### 1. Typewriter Effects

#### Basic Typewriter
```tsx
import { motion, useAnimation } from 'framer-motion';
import { useEffect, useState } from 'react';

export function Typewriter({
  text,
  speed = 50,
  delay = 0,
}: {
  text: string;
  speed?: number;
  delay?: number;
}) {
  const [displayText, setDisplayText] = useState('');
  const [isComplete, setIsComplete] = useState(false);

  useEffect(() => {
    const timeout = setTimeout(() => {
      let i = 0;
      const interval = setInterval(() => {
        if (i < text.length) {
          setDisplayText(text.slice(0, i + 1));
          i++;
        } else {
          setIsComplete(true);
          clearInterval(interval);
        }
      }, speed);

      return () => clearInterval(interval);
    }, delay);

    return () => clearTimeout(timeout);
  }, [text, speed, delay]);

  return (
    <span>
      {displayText}
      {!isComplete && (
        <motion.span
          animate={{ opacity: [1, 0] }}
          transition={{ duration: 0.5, repeat: Infinity }}
        >
          |
        </motion.span>
      )}
    </span>
  );
}
```

#### Typewriter with Delete
```tsx
export function TypewriterLoop({
  texts,
  typingSpeed = 50,
  deletingSpeed = 30,
  pauseDuration = 2000,
}: {
  texts: string[];
  typingSpeed?: number;
  deletingSpeed?: number;
  pauseDuration?: number;
}) {
  const [currentTextIndex, setCurrentTextIndex] = useState(0);
  const [displayText, setDisplayText] = useState('');
  const [isDeleting, setIsDeleting] = useState(false);

  useEffect(() => {
    const currentFullText = texts[currentTextIndex];

    const timeout = setTimeout(() => {
      if (!isDeleting) {
        if (displayText.length < currentFullText.length) {
          setDisplayText(currentFullText.slice(0, displayText.length + 1));
        } else {
          setTimeout(() => setIsDeleting(true), pauseDuration);
        }
      } else {
        if (displayText.length > 0) {
          setDisplayText(displayText.slice(0, -1));
        } else {
          setIsDeleting(false);
          setCurrentTextIndex((prev) => (prev + 1) % texts.length);
        }
      }
    }, isDeleting ? deletingSpeed : typingSpeed);

    return () => clearTimeout(timeout);
  }, [displayText, isDeleting, currentTextIndex, texts, typingSpeed, deletingSpeed, pauseDuration]);

  return (
    <span>
      {displayText}
      <motion.span
        className="inline-block w-0.5 h-[1em] bg-current ml-1"
        animate={{ opacity: [1, 0] }}
        transition={{ duration: 0.5, repeat: Infinity }}
      />
    </span>
  );
}
```

### 2. Character-by-Character Animations

#### Staggered Characters
```tsx
export function StaggeredText({
  text,
  staggerDelay = 0.03,
}: {
  text: string;
  staggerDelay?: number;
}) {
  const characters = text.split('');

  const containerVariants = {
    hidden: {},
    visible: {
      transition: { staggerChildren: staggerDelay },
    },
  };

  const charVariants = {
    hidden: { opacity: 0, y: 20 },
    visible: {
      opacity: 1,
      y: 0,
      transition: { type: 'spring', stiffness: 300, damping: 20 },
    },
  };

  return (
    <motion.span
      variants={containerVariants}
      initial="hidden"
      animate="visible"
      className="inline-block"
    >
      {characters.map((char, i) => (
        <motion.span
          key={i}
          variants={charVariants}
          className="inline-block"
          style={{ whiteSpace: char === ' ' ? 'pre' : 'normal' }}
        >
          {char}
        </motion.span>
      ))}
    </motion.span>
  );
}
```

#### Wave Text
```tsx
export function WaveText({ text }: { text: string }) {
  const characters = text.split('');

  return (
    <span className="inline-block">
      {characters.map((char, i) => (
        <motion.span
          key={i}
          className="inline-block"
          animate={{
            y: [0, -10, 0],
          }}
          transition={{
            duration: 0.6,
            delay: i * 0.05,
            repeat: Infinity,
            repeatDelay: 1,
          }}
          style={{ whiteSpace: char === ' ' ? 'pre' : 'normal' }}
        >
          {char}
        </motion.span>
      ))}
    </span>
  );
}
```

#### Bouncy Text
```tsx
export function BouncyText({ text }: { text: string }) {
  const characters = text.split('');

  return (
    <span className="inline-block">
      {characters.map((char, i) => (
        <motion.span
          key={i}
          className="inline-block"
          whileHover={{
            y: -10,
            color: '#667eea',
            transition: {
              type: 'spring',
              stiffness: 500,
              damping: 10,
            },
          }}
          style={{ whiteSpace: char === ' ' ? 'pre' : 'normal' }}
        >
          {char}
        </motion.span>
      ))}
    </span>
  );
}
```

### 3. Word-by-Word Animations

#### Staggered Words
```tsx
export function StaggeredWords({
  text,
  staggerDelay = 0.1,
}: {
  text: string;
  staggerDelay?: number;
}) {
  const words = text.split(' ');

  const containerVariants = {
    hidden: {},
    visible: {
      transition: { staggerChildren: staggerDelay },
    },
  };

  const wordVariants = {
    hidden: { opacity: 0, y: 20, filter: 'blur(10px)' },
    visible: {
      opacity: 1,
      y: 0,
      filter: 'blur(0px)',
      transition: { duration: 0.4, ease: 'easeOut' },
    },
  };

  return (
    <motion.span
      variants={containerVariants}
      initial="hidden"
      animate="visible"
      className="inline"
    >
      {words.map((word, i) => (
        <motion.span
          key={i}
          variants={wordVariants}
          className="inline-block mr-2"
        >
          {word}
        </motion.span>
      ))}
    </motion.span>
  );
}
```

#### Highlighted Words
```tsx
export function HighlightedText({
  text,
  highlightWords,
  highlightColor = '#667eea',
}: {
  text: string;
  highlightWords: string[];
  highlightColor?: string;
}) {
  const words = text.split(' ');

  return (
    <span>
      {words.map((word, i) => {
        const isHighlighted = highlightWords.some(
          (hw) => word.toLowerCase().includes(hw.toLowerCase())
        );

        return (
          <motion.span
            key={i}
            className="inline-block mr-2"
            initial={{ opacity: 0, y: 10 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ delay: i * 0.05 }}
          >
            {isHighlighted ? (
              <motion.span
                className="relative inline-block"
                whileHover={{ scale: 1.05 }}
              >
                <motion.span
                  className="absolute inset-0 -z-10 rounded"
                  style={{ backgroundColor: highlightColor }}
                  initial={{ scaleX: 0 }}
                  animate={{ scaleX: 1 }}
                  transition={{ delay: i * 0.05 + 0.2, duration: 0.3 }}
                />
                <span className="relative text-white px-1">{word}</span>
              </motion.span>
            ) : (
              word
            )}
          </motion.span>
        );
      })}
    </span>
  );
}
```

### 4. Text Scramble Effects

#### Scramble Text
```tsx
export function ScrambleText({
  text,
  duration = 1000,
}: {
  text: string;
  duration?: number;
}) {
  const [displayText, setDisplayTe

Related in General