Morphing Glyphs
NewFreeA 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
Installation
pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/morphing-glyphs.jsonNeeds 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/effectsimport { 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
AuroraInkChromeSunsetMono
Props
| Name | Type | Default | Description |
|---|---|---|---|
| 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. |
| interval | number | 2.4 | Seconds a word rests before it morphs into the next. Range 0.8 to 6 s. |
| morphDuration | number | 1.1 | Seconds one morph takes. Range 0.3 to 3 s. |
| blur | number | 0.5 | How far the words blur while they blend; scales with the font size. Range 0 to 1. |
| sharpness | number | 0.75 | Ink style: how hard the melted edge snaps, from smoky (0) to cut-paper crisp (1). Range 0 to 1. |
| glow | number | 0.6 | Glow 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. |
| color | string | '#f4f4f6' | Letter colour when the fill is solid. A hex colour. |
| colorA | string | '#3ef2d0' | Gradient start (the bright sky of the metal). A hex colour. |
| colorB | string | '#9b6bff' | Gradient end (the dark horizon of the metal). A hex colour. |
| angle | number | 100 | Direction of the gradient. Range 0 to 360 deg. |
| background | string | '#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. |
| size | number | 150 | Headline 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. |
| weight | number | 700 | Font weight. Range 300 to 900. |
| letterSpacing | number | -0.045 | Letter 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. |
| pauseOnHover | boolean | true | Hold the rotation while the pointer is over the word. |
| className | string | — | Extra class on the root element. The effect fills its parent: give the parent a height. |
| style | CSSProperties | — | Inline style on the root element. |
Files
What the command adds to your project, and where.
components/effects/morphing-glyphs/morphing-glyphs.tsxThe component.
components/effects/morphing-glyphs/text.tsxThe component.
components/effects/morphing-glyphs/showcase.tsxThe component.
components/effects/morphing-glyphs/engine.tsThe component.
components/effects/morphing-glyphs/look.tsThe component.
components/effects/morphing-glyphs/styles.tsThe component.
components/effects/morphing-glyphs/stage-styles.tsThe component.
lib/effect-runtime.tsShared 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.