WebberUI

Text Morph

A smooth morphing transition between two strings — shared characters slide to their new positions while the rest fade out and in, with auto-rotation and controlled switching.

Loading preview…
npx shadcn@latest add https://webberui.com/r/text-morph.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

3
0.5
10
<TextMorph />

Installation

npx shadcn@latest add https://webberui.com/r/text-morph.json

Or, once registries are configured in components.json, install it as @webberui/text-morph.

Usage

import { TextMorph } from "@/components/ui/text-morph";

// Auto-rotation (every 3 seconds by default)
<TextMorph
  texts={["Build faster.", "Ship sooner."]}
  className="text-4xl font-bold"
/>

// controlled mode: external state decides which string is shown
const [index, setIndex] = useState(0);
<TextMorph texts={plans} index={index} interval={0} />

Props

PropTypeDefaultDescription
textsstring[]The list of strings to morph through (at least one)
indexnumbercontrolled mode: the index currently shown (automatically taken modulo the length)
defaultIndexnumber0Initial index in uncontrolled mode
onIndexChange(index: number) => voidcallback fired when the index is about to change (from both auto-rotation and controlled switching)
intervalnumber3Auto-rotation interval (seconds); set to 0 to disable
durationnumber0.5Duration of a single morph animation (seconds)
ynumber10Vertical offset for characters entering and leaving (px)
blurbooleantrueWhether entering and leaving are accompanied by a blur
caseSensitivebooleanfalseWhether character matching is case-sensitive; when false, H slides into h
pauseOnHoverbooleantruePause auto-rotation while the mouse hovers

How it works

  • Character matching: each grapheme gets a stable key from "character + nth occurrence". Characters shared by the old and new strings slide smoothly to their new positions via Motion layout animation, and the rest drift up and out while new ones enter from below — producing a "morph" rather than a whole-page swap
  • Grapheme splitting: Intl.Segmenter splits on graphemes, so emoji, flags, and Chinese combining characters are never broken apart
  • controlled and uncontrolled modes: passing index makes it controlled. Auto-rotation reports the next index through onIndexChange in both modes, and the controlled side decides whether to accept it
  • Timer management: the rotation timer is cleaned up correctly on unmount, on hover pause, and on parameter changes, so nothing is left behind

Accessibility

  • When the user has "reduce motion" enabled at the system level, the text still rotates but switches instantly with no morph animation
  • The animation layer is hidden from assistive technology (aria-hidden), and the current complete string is exposed separately as sr-only, so what is announced is a complete sentence rather than scattered characters
  • pauseOnHover is on by default so users can stop the rotation to read; if the content matters, pairing it with controlled mode and explicit switching controls is recommended

On this page