
Torph is a lightweight text animation library that morphs one text string into another using smooth, physics-based motion.
It uses the browser’s Intl.Segmenter to split text into words or graphemes, animates exiting segments with translation, opacity, and scale, and provides spring easing for natural-looking motion.
Features:
- Morph changing text strings.
- Support React, Vue, Svelte, and vanilla JavaScript.
- Animate words or characters.
- Add spring-style motion.
- Handle locale-aware text segmentation.
- Respect reduced-motion preferences.
- Disable animation when needed.
- Attach lifecycle callbacks.
- Render custom HTML elements.
- Custom classes and inline styles.
How To Use It:
Installation
Install the Torph package for JavaScript, React, Vue, and Svelte projects.
# Yarn
$ yarn add torph
# NPM
$ npm install torph
# PNPM
$ pnpm install torph
Vanilla JavaScript
The vanilla JavaScript API works well when you need one animated text node in a non-framework page or a custom widget. Create the target element first, then call update() when the displayed text changes.
<div id="product-status"></div>
import { TextMorph } from "torph";
// Create a morph instance for an existing DOM element.
const statusMorph = new TextMorph({
element: document.getElementById("product-status"),
duration: 350,
ease: "cubic-bezier(0.19, 1, 0.22, 1)",
locale: "en",
});
// Replace the text and run the morph animation.
statusMorph.update("Sync Complete");
React Implementation
The component accepts the text as children and renders the selected HTML element through as.
import { useState } from "react";
import { TextMorph } from "torph/react";
export default function BillingHeader() {
const [planName, setPlanName] = useState("Starter Plan");
return (
<>
<TextMorph
as="h2"
duration={420}
ease="cubic-bezier(0.19, 1, 0.22, 1)"
locale="en"
className="billing-heading"
onAnimationComplete={() => console.log("Plan label updated")}
>
{planName}
</TextMorph>
<button onClick={() => setPlanName("Business Plan")}>
Change Plan
</button>
</>
);
}
The hook returns a ref and an update function for lower-level component composition.
import { useEffect } from "react";
import { useTextMorph } from "torph/react";
type StatusBadgeProps = {
statusText: string;
};
export function StatusBadge({ statusText }: StatusBadgeProps) {
const { ref, update } = useTextMorph({
duration: 300,
ease: "cubic-bezier(0.19, 1, 0.22, 1)",
});
useEffect(() => {
// Send new text to the morph controller.
update(statusText);
}, [statusText, update]);
return <span ref={ref} className="status-badge" />;
}
Vue Implementation
A Vue component can pass reactive text into the Torph component. Keep the text value as a string and update the ref when the UI state changes.
<script setup>
import { ref } from "vue";
import { TextMorph } from "torph/vue";
const checkoutStep = ref("Shipping Details");
function goToPayment() {
checkoutStep.value = "Payment Method";
}
</script>
<template>
<TextMorph
-text="checkoutStep"
-duration="400"
ease="cubic-bezier(0.19, 1, 0.22, 1)"
locale="en"
class="checkout-step-title"
as="h3"
/>
<button @click="goToPayment">Next Step</button>
</template>
Svelte Implementation
A Svelte view can pass state into the Torph component. This works for Svelte 5 state syntax and keeps the morph target inside the component template.
<script>
import { TextMorph } from "torph/svelte";
let campaignLabel = $state("Draft Campaign");
function publishCampaign() {
campaignLabel = "Published Campaign";
}
</script>
<TextMorph
text={campaignLabel}
duration={380}
ease="cubic-bezier(0.19, 1, 0.22, 1)"
locale="en"
class="campaign-title"
as="h2"
/>
<button on:click={publishCampaign}>Publish</button>
Spring motion
Spring motion is great for UI text that should feel less linear than a fixed CSS easing curve. Pass spring parameters to the easing option and Torph computes the animation duration from the spring values.
import { useState } from "react";
import { TextMorph } from "torph/react";
export function MetricLabel() {
const [metricName, setMetricName] = useState("Monthly Revenue");
return (
<TextMorph
as="strong"
ease={{
stiffness: 180,
damping: 18,
mass: 1,
precision: 0.001,
}}
>
{metricName}
</TextMorph>
);
}
Configuration Options
text/children(string): Sets the text content to display. Framework components use the format that matches the framework.duration(number): Sets the animation duration in milliseconds. The default value is400.ease(string | SpringParams): Sets a CSS easing function or spring motion settings. The default value is"cubic-bezier(0.19, 1, 0.22, 1)".scale(boolean): Adds scale animation to exiting text segments. The default value istrue.locale(Intl.LocalesArgument): Sets the locale for text segmentation. The default value is"en".debug(boolean): Adds visual debug indicators around the root and text items.disabled(boolean): Turns off morphing animations and updates the text directly. The default value isfalse.respectReducedMotion(boolean): Checks the user’s reduced-motion preference and disables animation when reduction is requested. The default value istrue.onAnimationStart(() => void): Runs when an animation starts after the first render.onAnimationComplete(() => void): Runs when the size transition and morph animation complete.className(string): Adds a CSS class in React. Useclassin Vue and Svelte templates.style(object | string): Adds inline styles through the supported framework or DOM setup.as(string): Sets the rendered HTML element. The default value is"span".
Spring Parameters
stiffness(number): Sets the spring stiffness coefficient. The default value is100.damping(number): Sets the damping coefficient. The default value is10.mass(number): Sets the spring mass. The default value is1.precision(number): Sets the threshold used to determine the settled position. The default value is0.001.
API Methods
import { TextMorph, MorphController } from "torph";
// Create a vanilla JavaScript morph instance.
const headingMorph = new TextMorph({
element: document.getElementById("release-heading"),
duration: 400,
ease: "cubic-bezier(0.19, 1, 0.22, 1)",
});
// Replace the current text and run the morph animation.
headingMorph.update("Version 2.4 Released");
// Stop active animations and clean up the instance.
headingMorph.destroy();
// Create a lower-level controller for custom integrations.
const controller = new MorphController();
// Attach the controller to an element with Torph options.
controller.attach(document.getElementById("account-label"), {
duration: 320,
locale: "en",
});
// Update the controlled text node.
controller.update("Account Verified");
// Check whether a new option set requires a recreated instance.
const shouldRecreate = controller.needsRecreate({
duration: 500,
locale: "en",
scale: true,
debug: false,
disabled: false,
respectReducedMotion: true,
ease: "cubic-bezier(0.19, 1, 0.22, 1)",
});
// Destroy the controller and its internal morph instance.
controller.destroy();
Alternatives:
- 10 Best Typewriter Text Animation JavaScript Libraries
- Apply Smooth Animations to Text Using Data Attributes
- JavaScript Library For Beautiful Smooth Text Animations
- Text Shuffle Animation In Pure JavaScript – shuffle-text.js
- Text Scramble/Shuffle Effect – scrambling-text-js
- Animate Text Word By Word – movinwords.js
- Advanced Typewriter Animation Library – TypeFX.js







