Morphing Text
ericts-ui/text-morphFreeComponent
A Motion-powered text primitive that morphs matching characters between string updates.
Live preview
live · rendered by the registry
Rendered by ericts-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.ericts.com/r/text-morph.jsonSource
1"use client";23import * as React from "react";4import { AnimatePresence, motion, useReducedMotion } from "motion/react";56import { cn } from "@/lib/utils";78export type TextMorphProps = Omit<9 React.ComponentPropsWithoutRef<"span">,10 "children"11> & {12 children: string | string[];13};1415function getText(children: TextMorphProps["children"]) {16 return Array.isArray(children) ? children.join("") : children;17}1819function generateKeys(text: string) {20 const charCount: Record<string, number> = {};2122 return text.split("").map((char) => {23 charCount[char] ??= 0;2425 const key = `${char}-${charCount[char]}`;26 charCount[char] += 1;2728 return { char, key };29 });30}3132export function TextMorph({ children, className, ...props }: TextMorphProps) {33 const uid = React.useId();34 const shouldReduceMotion = useReducedMotion();35 const text = getText(children);36 const textToDisplay = React.useMemo(() => generateKeys(text), [text]);37 const transition = shouldReduceMotion38 ? { duration: 0 }39 : ({40 duration: 0.25,41 type: "spring",42 bounce: 0,43 opacity: {44 duration: 0.35,45 type: "spring",46 bounce: 0,47 },48 } as const);4950 return (51 <span aria-label={text} className={cn("inline-block", className)} {...props}>52 <AnimatePresence mode="popLayout" initial={false}>53 {textToDisplay.map(({ char, key }) => (54 <motion.span55 key={key}56 layoutId={shouldReduceMotion ? undefined : `${uid}-${key}`}57 aria-hidden="true"58 className="inline-block text-inherit"59 initial={shouldReduceMotion ? false : { opacity: 0 }}60 animate={{ opacity: 1 }}61 exit={shouldReduceMotion ? undefined : { opacity: 0 }}62 transition={transition}63 >64 {char === " " ? "\u00A0" : char}65 </motion.span>66 ))}67 </AnimatePresence>68 </span>69 );70}
What it pulls in
npm packages
Files it writes
More from ericts-ui
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Adaptive Draweradaptive-drawer | ericts-ui | Components | Free | 2 deps | |
| Check Animationcheck-animation | ericts-ui | Components | Free | no deps · 2 files | |
| Context Cursorcontext-cursor | ericts-ui | Components | Free | 1 dep | |
| Copy Buttoncopy-button | ericts-ui | Components | Free | 2 deps | |
| Expandable Dialogexpandable-modal | ericts-ui | Components | Free | 1 dep | |
| Expandable Tabsexpandable-tabs | ericts-ui | Components | Free | 1 dep | |
| Expandable Toolbarexpandable-toolbar | ericts-ui | Components | Free | 1 dep | |
| Expanding Panelexpanding-panel | ericts-ui | Components | Free | 2 deps |
