A word that liquid-morphs into the next: ink that melts, light that blooms or a motion-blurred slide. A hero-headline rotator that holds its sentence steady and keeps real text for screen readers.

  • text
  • heading
  • morph
  • gooey
  • rotator
  • hero
  • headline
  • typography
  • svg
Preset

Installation

pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/morphing-glyphs.json

Needs a React + Tailwind project set up with shadcn. Copies the effect into your project, along with lib/effect-runtime.ts, the small WebGL layer it runs on. No other dependencies.

Or from npm

pnpm add @kagadmodyaa/effects
import { MorphingText } from '@kagadmodyaa/effects/morphing-glyphs'

One package with every free effect, one import path each. No Tailwind or shadcn needed, and no stylesheet to import: the few layout rules an effect needs come with it. The npm package and the command above ship the same source.

Usage

Use it like any other component. Choose a theme with preset; any prop you pass as well overrides the preset.

import { MorphingText } from '@/components/effects/morphing-glyphs/text'

export function Hero() {
  return (
    <>
      {/* A hero headline word that melts into the next */}
      <h1 style={{ fontSize: 96 }}>
        Make it{' '}
        <MorphingText words={['clearer', 'faster', 'yours']} preset="Sunset" />
      </h1>

      {/* Inside a sentence: the box has the word's width and sits on the text baseline */}
      <p>
        We help teams <MorphingText words={['design', 'build', 'ship']} variant="glow" preset="Aurora" /> faster.
      </p>

      {/* Reserve the widest word's width so nothing around it moves */}
      <MorphingText as="h2" words={['Ink', 'Paper', 'Press']} preset="Ink" width="max" interval={3} />
    </>
  )
}

// Imperative control, and a callback when a morph starts:
//   const ref = useRef<MorphingTextHandle>(null)
//   <MorphingText ref={ref} words={words} autoplay={false} onChange={(i, word) => console.log(i, word)} />
//   <button onClick={() => ref.current?.next()}>Next</button>   // also prev() and goTo(2)
// The stage above is <MorphingGlyphsShowcase />, also exported, if you want the same hero.
<MorphingText preset="Ink" wordSet={"Design · Build · Ship"} />

Presets

  • Aurora
  • Ink
  • Chrome
  • Sunset
  • Mono

Props

NameTypeDefaultDescription
preset'Aurora' | 'Ink' | 'Chrome' | 'Sunset' | 'Mono'none (the defaults below)A named theme. Any prop you pass as well overrides the preset’s value.
wordSet'Design · Build · Ship' | 'Sketch · Refine · Polish' | 'Calm · Bold · Playful' | 'Ideas · Systems · Stories''Design · Build · Ship'Which word list the stage rotates. MorphingText itself takes any string[] as words.
variant'ink' | 'glow' | 'slide-blur''glow'How a word becomes the next: ink melts through an SVG threshold, glow blooms through a colour fringe, slide-blur rolls vertically with motion blur.
intervalnumber2.4Seconds a word rests before it morphs into the next. Range 0.8 to 6 s.
morphDurationnumber1.1Seconds one morph takes. Range 0.3 to 3 s.
blurnumber0.5How far the words blur while they blend; scales with the font size. Range 0 to 1.
sharpnessnumber0.75Ink style: how hard the melted edge snaps, from smoky (0) to cut-paper crisp (1). Range 0 to 1.
glownumber0.6Glow style: strength of the halo and the red/blue edge fringe. Range 0 to 1.
fill'solid' | 'gradient' | 'metal''gradient'One colour, a two-colour gradient through the letters, or a banded chrome built from the two colours.
colorstring'#f4f4f6'Letter colour when the fill is solid. A hex colour.
colorAstring'#3ef2d0'Gradient start (the bright sky of the metal). A hex colour.
colorBstring'#9b6bff'Gradient end (the dark horizon of the metal). A hex colour.
anglenumber100Direction of the gradient. Range 0 to 360 deg.
backgroundstring'#05060d'Page colour of the stage and gallery card. The component itself is transparent. A hex colour.
font'display' | 'serif' | 'mono' | 'inherit''display'Typeface of the word; inherit takes it from the parent.
sizenumber150Headline size in px at a 1000px wide stage; the showcase scales it with its width. MorphingText itself takes any CSS length, or the parent's size. Range 60 to 240 px.
weightnumber700Font weight. Range 300 to 900.
letterSpacingnumber-0.045Letter spacing in em. Range -0.08 to 0.2 em.
width'auto' | 'max''auto'auto: the box eases to the width of each word. max: it reserves the width of the longest word, so the sentence never moves.
pauseOnHoverbooleantrueHold the rotation while the pointer is over the word.
classNamestring—Extra class on the root element. The effect fills its parent: give the parent a height.
styleCSSProperties—Inline style on the root element.

Files

What the command adds to your project, and where.

  • components/effects/morphing-glyphs/morphing-glyphs.tsx

    The component.

  • components/effects/morphing-glyphs/text.tsx

    The component.

  • components/effects/morphing-glyphs/showcase.tsx

    The component.

  • components/effects/morphing-glyphs/engine.ts

    The component.

  • components/effects/morphing-glyphs/look.ts

    The component.

  • components/effects/morphing-glyphs/styles.ts

    The component.

  • components/effects/morphing-glyphs/stage-styles.ts

    The component.

  • lib/effect-runtime.ts

    Shared by every shader effect: resizing, pausing, reduced motion, context loss and the no-WebGL fallback. Installed once.

Behaviour

  • The real word is always in the DOM. A visually hidden aria-live="polite" region carries the current word (updates are throttled to about one a second); the drawn letters are aria-hidden. The first word is rendered by the server and all the animation starts after mount, so there is no hydration mismatch.
  • Ink draws the outgoing and incoming word on two layers with animated blur and opacity under an SVG alpha-threshold filter, so strokes melt into liquid blobs and snap back. The filter is only on while a morph runs: at rest the word is plain, crisp type.
  • Glow blooms the two words through each other additively, with a halo and a red/blue edge fringe that peak mid-morph. Slide-blur rolls the words vertically with a true vertical-only motion blur. A WebGL pass was left out on purpose: the SVG and CSS version is sharper at any size and costs nothing to install.
  • The box is an inline-block with an invisible in-flow copy of the word, so it takes the word's font, line-height and text baseline and sits in a sentence like any other word. width="auto" eases the box's width between the (once measured) widths of the words; width="max" reserves the longest word through a CSS grid, so even the server render holds the full width.
  • One requestAnimationFrame loop, only while a morph runs. Rotation pauses while the word is offscreen, in a hidden tab and, if pauseOnHover, under the pointer, and resumes where it left off.
  • next(), prev() and goTo(i) are on the ref; calls made during a morph queue the latest target. loop={false} stops on the last word; autoplay={false} leaves the changes to you.
  • Under prefers-reduced-motion the word does not rotate by itself, and next(), prev() and goTo() swap with a short fade instead of a morph.

See all effects