Command Palette

Search for a command to run...

Text Loop

An interactive collection of stacked headings that reveal individual content sections with a click.

How can I assist you today?

Installation

Install the following dependencies:

pnpm add motion

Create a file at components/motion/text-loop.tsx and paste the given code.

"use client";
import { cn } from "@/lib/utils";
import {
    motion,
    AnimatePresence,
    Transition,
    Variants,
    AnimatePresenceProps,
} from "motion/react";
import { useState, useEffect, Children } from "react";
 
export type TextLoopProps = {
    children: React.ReactNode[];
    className?: string;
    interval?: number;
    transition?: Transition;
    variants?: Variants;
    onIndexChange?: (index: number) => void;
    trigger?: boolean;
    mode?: AnimatePresenceProps["mode"];
};
 
export function TextLoop({
    children,
    className,
    interval = 2,
    transition = { duration: 0.6 },
    variants,
    onIndexChange,
    trigger = true,
    mode = "popLayout",
}: TextLoopProps) {
    const [currentIndex, setCurrentIndex] = useState(0);
    const items = Children.toArray(children);
 
    useEffect(() => {
        if (!trigger) return;
 
        const intervalMs = interval * 1000;
        const timer = setInterval(() => {
            setCurrentIndex((current) => {
                const next = (current + 1) % items.length;
                onIndexChange?.(next);
                return next;
            });
        }, intervalMs);
        return () => clearInterval(timer);
    }, [items.length, interval, onIndexChange, trigger]);
 
    const motionVariants: Variants = {
        initial: { y: 20, opacity: 0 },
        animate: { y: 0, opacity: 1 },
        exit: { y: -20, opacity: 0 },
    };
 
    return (
        <div
            className={cn("relative inline-block whitespace-nowrap", className)}
        >
            <AnimatePresence mode={mode} initial={false}>
                <motion.div
                    key={currentIndex}
                    initial="initial"
                    animate="animate"
                    exit="exit"
                    transition={transition}
                    variants={variants || motionVariants}
                >
                    {items[currentIndex]}
                </motion.div>
            </AnimatePresence>
        </div>
    );
}

Update the import paths to match your project setup.