MorphingText

A React text animation component that cycles your words, each one melting into the next. For hero headlines.

MorphingText playground

Hover the word to hold it; a morph already in flight always finishes.

Install MorphingText

1. New project? One command installs the package and wires everything - see Installation:

bash
pnpm dlx zyncat-ui init

2. Import the component - it loads its own stylesheet:

tsx
import { MorphingText } from '@zyncat/ui/morphing-text';

Usage

tsx
import { MorphingText } from '@zyncat/ui/morphing-text';
 
<MorphingText words={['Weight', 'Timing', 'Ease', 'Rest']} />

Compose MorphingText in your application. No Tailwind or external styling library is required; every value resolves from Zyncat UI's token vocabulary.

MorphingText props

MorphingText

PropTypeDefaultDescription
...htmlAttributesHTMLAttributes—Standard <span> attributes (aria-*, data-*, title, ...) forwarded to the root.
words*string[]—The words to cycle through, in order; the last one morphs back into the first. Fewer than two words renders as still type.
holdnumber1500How long a word rests fully legible before the next morph starts, in milliseconds; sampled live.
speednumber1Multiplies the simulation rate; sampled live on every frame.
classNamestring—Extra class(es) merged onto the root.
styleMorphingTextStyle—Inline styles merged onto the root.

MorphingTextStyle

PropTypeDefaultDescription
--morphing-text-inkstring | number—`--morphing-text-ink` - Letter ink. Default: `var(--text-strong)`.
--morphing-text-sizestring | number—`--morphing-text-size` - Letter size. Default: `var(--size-display-lg)`.
--morphing-text-weightstring | number—`--morphing-text-weight` - Letter weight. Default: `var(--weight-medium)`.
--morphing-text-leadingstring | number—`--morphing-text-leading` - Line height, unitless. Default: `1.2`.
--morphing-text-trackingstring | number—`--morphing-text-tracking` - Letter spacing. Default: `var(--tracking-display)`.
--morphing-text-smearstring | number—`--morphing-text-smear` - How much letters blur between words, 0 cutting clean. Default: `1`.
--morphing-text-rule-inkstring | number—`--morphing-text-rule-ink` - The rule at rest. Default: `var(--text-strong)`.
--morphing-text-rule-accentstring | number—`--morphing-text-rule-accent` - The rule while a morph heats it. Default: `var(--accent)`.
--morphing-text-rule-heightstring | number—`--morphing-text-rule-height` - The rule's thickness. Default: `1px`.
--morphing-text-rule-gapstring | number—`--morphing-text-rule-gap` - Space between the word and the rule. Default: `var(--space-3)`.

Frequently asked questions

Import it per subpath and pass a word list: import { MorphingText } from '@zyncat/ui/morphing-text', then <MorphingText words={['Weight', 'Timing', 'Ease']} />. It is a client component, so it drops straight into the Next.js App Router; link @zyncat/ui/styles.css once at the root for the tokens.

Every letter of the outgoing and incoming word is blurred on its own stagger, and the whole stage passes through one SVG filter - an feGaussianBlur feeding an feColorMatrix alpha threshold - so the softened letterforms pool and split like liquid. The filter attaches only while a morph is in flight, so a resting word is untouched, unblurred type.

Yes. hold sets how long each word rests fully legible before the next morph starts, in milliseconds (default 1500), and speed multiplies the simulation rate (default 1). Both are sampled live on every frame, so changing either retimes the animation mid-morph; the morph itself runs 900ms.

Neither - it is a React component that morphs live DOM text, not a GIF, a Lottie file, or an After Effects or Photoshop render, so the words stay real text rather than pixels. The playground on this page is the generator: set the words, hold and speed, then copy the JSX from the Code tab.

Not this effect. The look is CSS - the letter blur, the colour and the hairline all read from --morphing-text-* custom properties you can retune - but the per-letter stagger, the SVG threshold strength and the rule that stretches between the two word widths are measured and driven in JavaScript every frame. A pure CSS text animation can cross-fade two words; it cannot pool their letterforms.

Yes. The current word is rendered as real text in a visually hidden label for screen readers while the animated stage is aria-hidden, so assistive tech reads one word instead of a stream of letters. Under prefers-reduced-motion the loop never starts and the first word shows as still, unblurred type.

Set in Geist & Newsreader — animated by the house engineZyncat UI · Rev 0.11 · MIT · Built by Tabsir Ahammed · Source on GitHub