Word Loader

Kinetic typography flip loader that tumbles through waiting state words with spring blur physics.

ReactTailwind CSSFramer Motion
Dependenciesframer-motion, lucide-react
npm install framer-motion lucide-react

Preview

Crafting
compiling components
...

Rotating every 2.2s with spring blur tumble

Source Code

word-loader.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
"use client";

import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Sparkles, Terminal } from "lucide-react";

export interface WordLoaderProps {
  prefix?: string;
  words?: string[];
  suffix?: string;
  interval?: number;
}

export function WordLoader({
  prefix = "Crafting",
  words = ["compiling components", "synthesizing springs", "optimizing layout"],
  suffix = "...",
  interval = 2200,
}: WordLoaderProps) {
  const [index, setIndex] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setIndex((prev) => (prev + 1) % words.length);
    }, interval);

    return () => clearInterval(timer);
  }, [words.length, interval]);

  return (
    <div className="inline-flex items-center gap-2 px-4 py-2.5 rounded-2xl bg-zinc-100 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 shadow-sm text-sm">
      <Terminal className="w-4 h-4 text-zinc-400 shrink-0" />
      <span className="font-medium text-zinc-500">{prefix}</span>
      <div className="relative h-6 min-w-[200px] overflow-hidden flex items-center">
        <AnimatePresence mode="wait">
          <motion.span
            key={words[index]}
            initial={{ y: 20, opacity: 0, filter: "blur(4px)" }}
            animate={{ y: 0, opacity: 1, filter: "blur(0px)" }}
            exit={{ y: -20, opacity: 0, filter: "blur(4px)" }}
            transition={{ type: "spring", stiffness: 400, damping: 28 }}
            className="absolute font-semibold text-zinc-900 dark:text-zinc-100 whitespace-nowrap"
          >
            {words[index]}
          </motion.span>
        </AnimatePresence>
      </div>
      <span className="font-medium text-zinc-400">{suffix}</span>
    </div>
  );
}

Usage

usage.tsx
1
2
3
4
5
<WordLoader
  prefix="Generating"
  words={["neural tokens", "layout tree", "spring physics"]}
  interval={2000}
/>

Props

PropTypeRequiredDefaultDescription
prefixstringOptional"Crafting"Static leading text.
wordsstring[]OptionaldefaultWordsList of words cycling through the tumble flip animation.
intervalnumberOptional2200Interval in milliseconds between word transitions.

Types

types.ts
1
2
3
4
5
6
export interface WordLoaderProps {
  prefix?: string;
  words?: string[];
  suffix?: string;
  interval?: number;
}