A phrase that blurs and cross-fades into the next phrase in a list, in the same spot. The slot is as wide as the longest phrase, so nothing around it jumps.
Optional, and kept apart.
Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.
import { MorphingText } from "@rdloom/react";
export default function MorphingTextBasicExample() {
return (
<div className="flex w-full justify-center text-3xl font-semibold">
<MorphingText texts={["Design", "Build", "Ship", "Repeat"]} />
</div>
);
}Installation
npx rdloom add morphing-textCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.
It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/morphing-text.jsonWorks, but without upgrade tracking, and you import the motion CSS yourself.
Usage
import { MorphingText } from "@rdloom/react";
<div className="flex w-full justify-center text-3xl font-semibold">
<MorphingText texts={["Design", "Build", "Ship", "Repeat"]} />
</div>Slow and large
import { MorphingText } from "@rdloom/react";
// A longer interval and a slower morph suit a large hero line.
export default function MorphingTextSlowAndLargeExample() {
return (
<div className="flex w-full justify-center text-5xl font-bold">
<MorphingText texts={["Fewer clicks", "Faster pages", "Calmer teams"]} interval={4000} duration={1400} />
</div>
);
}Paused
import { useState } from "react";
import { Button, MorphingText } from "@rdloom/react";
// Anything that keeps moving should be stoppable: isPaused holds the current phrase.
export default function MorphingTextPausedExample() {
const [paused, setPaused] = useState(false);
return (
<div className="flex w-full flex-col items-center gap-4">
<div className="text-3xl font-semibold">
<MorphingText texts={["Plan", "Draft", "Review", "Publish"]} isPaused={paused} />
</div>
<Button variant="ghost" size="sm" onPress={() => setPaused((p) => !p)} aria-pressed={paused}>
{paused ? "Resume motion" : "Pause motion"}
</Button>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
textsrequiredThe phrases to morph through, in order. | string[] | none |
intervalMilliseconds each phrase stays before the next one replaces it. | number | 3000 |
durationMilliseconds the blur and cross-fade take. | number | 900 |
labelAccessible name for the whole slot. Defaults to all the phrases, joined with commas. | string | none |
isPausedStops the animation and shows the finished text. Moving content that lasts more than five seconds should be stoppable. | boolean | false |
Accessibility
Role group, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
Not interactive.
Screen readers announce
- The list of all phrases, once
What your code must do
- The slot has an accessible name listing every phrase once, and the visible layers are hidden from assistive technology
- Hidden sizing copies of the other phrases are invisible to everyone, so nothing is read twice
- Decoration only: the animated layers are hidden from assistive technology and never take focus
- Stands still for visitors who ask for less motion (prefers-reduced-motion), and can also be shown still with the rdm-still class or isPaused
- In forced-colors (high contrast) mode the text is drawn in the system colours with no effect
- Does not flash: nothing changes more than three times in a second (WCAG 2.3.1)
- The text is real text in the server HTML, readable with no script
- Optional: it is a separate component, so projects that do not add it carry none of its code
Guidelines
Use it when
- A hero line that cycles through a few short statements
- Showing a range of things a product handles
Avoid it when
- Phrases that carry information people must not miss
- Lists longer than six phrases
Don't
- Putting a different meaning in each phrase and expecting every one to be read
- Very short intervals, which make the text hard to read
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-text-default