Introducing Motion-Primitives Pro - Advanced components and templates to help you build a website that stands out.

Text Morph

Animates text by morphing shared letters between words, creating fluid transitions

Examples

Text Morph Button

Text Morph Input

Code

'use client';
import { cn } from '@/lib/utils';
import { AnimatePresence, motion } from 'framer-motion';
import { useMemo, useId } from 'react';

type TextMorphProps = {
  children: string;
  as?: React.ElementType;
  className?: string;
  style?: React.CSSProperties;
};

export function TextMorph({
  children,
  as: Component = 'p',
  className,
  style,
}: TextMorphProps) {
  const uniqueId = useId();

  const characters = useMemo(() => {
    const charCounts: Record<string, number> = {};

    return children.split('').map((char, index) => {
      const lowerChar = char.toLowerCase();
      charCounts[lowerChar] = (charCounts[lowerChar] || 0) + 1;

      return {
        id: `${uniqueId}-${lowerChar}${charCounts[lowerChar]}`,
        label: index === 0 ? char.toUpperCase() : lowerChar,
      };
    });
  }, [children, uniqueId]);

  return (
    <Component className={cn(className)} aria-label={children} style={style}>
      <AnimatePresence mode='popLayout' initial={false}>
        {characters.map((character) => (
          <motion.span
            key={character.id}
            layoutId={character.id}
            className='inline-block'
            aria-hidden='true'
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{
              type: 'spring',
              stiffness: 280,
              damping: 18,
              mass: 0.3,
            }}
          >
            {character.label}
          </motion.span>
        ))}
      </AnimatePresence>
    </Component>
  );
}

Please add:

Component API

TextMorph

PropTypeDefaultDescription
childrenstringThe text content to be animated.
askeyof JSX.IntrinsicElements'p'The HTML tag to render, defaults to paragraph.
classNamestringundefinedOptional CSS class for styling the component.
styleReact.CSSPropertiesundefinedOptional inline styles for the component.

Credits

Inspired by Family iOS app. See also Family Values