Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Text

Pro

Writing Text for React

Text written out a word at a time, at the rhythm of reading it aloud. For React with Base UI, when a tagline or a quote should arrive calmly.

Framework

Writing Text brings a sentence in word by word, each word rising softly into place at the pace of someone reading it: a long word holds the next one back a little longer than a short one, and a comma or a full stop adds a pause. It plays once, as soon as the page shows it or when it scrolls into view.

Use it for a tagline, a quote or a short paragraph that should feel spoken rather than animated. For an even beat, word after word, use Splitting Text with by="words"; for words that come into focus from a blur, Blur Text; to type the text out a character at a time, Typing Text. The motion is a CSS keyframe animation the install adds to your stylesheet, each word delayed by the characters and pauses before it, with no animation library. With reduced motion the text is simply there.

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Usage

import { WritingText } from "@/components/ui/writing-text";
<p>
  <WritingText text="Some ideas need a moment. Let them arrive, one word at a time." />
</p>

Demos

Speed

speed is the time each character holds the next word back, 30 ms by default. A low speed suits a label you glance at; a high one, a line meant to be read slowly.

Preview width

Lines

Line breaks in the text (\n) are kept, and each one pauses like the end of a sentence, so a quote or a verse arrives a line at a time.

Preview width

Accessibility

Screen readers read the whole text once, as plain text: the rising words are hidden from them, and a visually hidden copy carries it. Words wrap as usual, and the text keeps its direction on a right-to-left page. With reduced motion every word is in place from the start.

FAQ

How is each word's timing worked out?

A word starts once the words before it have had their time: one speed for each character and space before it, ten more after a sentence's end (., !, ?) or a line break, and five after a comma, a semicolon, a colon or a dash. The text sets the rhythm, so it reads like speech.

How is it different from Splitting Text by words?

Splitting Text gives every word the same beat and slides it in from further away. Writing Text times each word by the text before it and only lifts it a little, so it reads calmer, closer to a voice than to a sequence.

Does it suit a long paragraph?

A few sentences at most: at the default speed, a sixty-character sentence takes between two and three seconds to arrive. For a longer text, lower speed, or use Text Generate Effect, whose words follow each other quickly.

Can I change how each word rises?

duration is how long one word takes to rise, 800 ms by default. In your CSS, --writing-text-y is how far below it starts (0.35em).

When does the animation play?

With trigger="mount", the default, as soon as the page shows it: the animation runs from CSS alone, before the page's script loads. With trigger="view" it waits until a quarter of the text has scrolled into view, then plays once.

How do I run something after it finishes?

Pass onComplete: it's called once, when the last word is in place.

What happens with reduced motion?

The words don't rise: the text is there from the start, fully readable.

API Reference

WritingText

<span> data-slot="writing-text"

PropType
textstring

The text. Its line breaks (\n) are kept.

duration?number

How long one word takes to rise into place, in milliseconds. @default 800.

onComplete?() → void

Called once the last word is in place.

Returns
void
speed?number

The time each character holds the next word back, in milliseconds: a long word waits longer than a short one, and a comma or a full stop adds a pause, as in speech.

Default
30
trigger?"mount" | "view"

When it plays: as soon as it's on the page, or once it scrolls into view.

Type
"mount" | "view"
Default
"mount"
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-last?string
data-view?string

"in" when visible is true, otherwise "out".

CSS variableType
--writing-text-durationstring

One word's rise.

--writing-text-speedstring

The time each character holds the next word back.

--writing-text-ystring

How far below each word starts (0.35em).

All 97 text effects