Skip to content

TextEffect

Render any text with a reveal: a split-flap board, a terminal, a decode, a blur-in or rolling reels.

Use it wherever a short piece of text arrives or changes and the change is worth seeing: a hero headline, a stat, a status line, a departures row. Pick the effect by what the text is. A board or a code reads well as split-flap or roll, a command or log line as terminal, a technical label as decode, and a headline or sentence as blur-in. Keep it to a few words or one sentence, and don't animate text people need to read at once, like an error message.

Installation

npm i @youj/motion

import { TextEffect } from '@youj/motion'

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Usage

import { TextEffect } from '@youj/motion'

export function Hero() {
  return <TextEffect as="h1" text="Interfaces that move with intent" />
}

It plays when half of it is on screen (inView; inView={false} plays on mount) and again whenever text changes, from the text it showed to the new one. The final text holds its box from the first frame, so nothing around it moves.

Examples

Effects

effect picks one of five:

  • blur-in, the default: word by word, each sharpening out of a blur as it rises a little.
  • split-flap: each character on a tile that flips through its alphabet to land, top half then bottom, across a hinge.
  • terminal: typed out a keystroke at a time behind an optional prompt, with a block cursor that blinks while it waits.
  • decode: random glyphs resolving into place. This is ScrambleText, with TextEffect's box and timing.
  • roll: each character on a reel that rolls up to it, like a slot or an odometer.

split-flap and roll pass through the target's own digits or letters (A–Z, a–z or 0–9), or through glyphs when you set one. A space or punctuation mark changes in one step, and a character that stays the same doesn't move.

Reference

Propstext, effect, as, inView… (11)

Props

TextEffect props
PropType
textRequiredstring

The text. Changing it plays the effect from the old text to the new one.

–
effectTextEffectName
'split-flap''terminal''decode''blur-in''roll'

How the text arrives.

default 'blur-in'

'blur-in'
asElementType

default 'span'

'span'
inViewInView

When the first play starts: false on mount; true (the default) when scrolled into view, at 50% visible; or { once, amount, margin }.

–
durationDurationToken
'instant''fast''base''slow''deliberate'

One unit's motion: a flap's flip (split-flap, default 'fast'), a reel's roll (roll, 'deliberate'), a word's blur-in ('slow') or the whole decode ('deliberate'). Terminal doesn't use it.

–
staggerStaggerToken
'tight''base''loose'

The gap between units: characters (split-flap 'base', roll 'tight'), words (blur-in 'loose') or keystrokes (terminal 'loose', varied per key). A long text is compressed to land within the deliberate token. Decode doesn't use it.

–
glyphsstring

The alphabet decode draws noise from, and the one split-flap and roll pass through. Default: per character, its own digits, upper- or lower-case letters.

–
promptstring

Terminal: a prefix shown before the text and never typed, such as $ .

–
cursorboolean

Terminal: the block cursor.

default true

true
eraseboolean

Terminal: on a text change, delete back to the shared start before typing the new text.

default false

false
onComplete() => void

Called when a play lands on the text (at once under reduced motion).

–
Other props pass through: extends HTMLAttributes<HTMLElement>.
MotionPer effect · reduced motion

Motion

duration is one unit's motion and stagger the gap between units. Each effect has its own defaults:

Motion tokens
MomentTokens
Split-flap flipeach flip; up to six per character, characters staggered
duration.faststagger.base

140ms · 40ms step

140ms40ms step
Rolleach reel, characters staggered
duration.deliberateease.enterstagger.tight

560ms · cubic-bezier(0.16, 1, 0.3, 1) · 24ms step

560mscubic-bezier(0.16, 1, 0.3, 1)24ms step
Blur-ineach word: blur, rise and fade, words staggered
duration.slowease.enterstagger.loose

340ms · cubic-bezier(0.16, 1, 0.3, 1) · 72ms step

340mscubic-bezier(0.16, 1, 0.3, 1)72ms step
Terminal keystrokeuneven per key, longer after punctuation; erase deletes at twice the speed
stagger.loose

72ms step

72ms step
Decodethe whole decode
duration.deliberate

560ms

560ms

Reduced motion

The final text shows at once, and new text swaps in place. The cursor holds still.

A long text keeps its stagger within the deliberate token, so twenty tiles land about as soon as five. Every effect runs on one animation frame loop per instance and moves only transforms, opacity and filter; the cursor blinks only while it's on screen.

Reduced motion

The text renders as it is, with no flips, typing, glyphs, blur or reels, and onComplete is called at once. A board shows its tiles and a terminal its prompt and a still cursor, so the look stays.

AccessibilityARIA, selection, direction

Accessibility

  • The text is rendered once in a visually hidden span, and the animated layer is aria-hidden. A screen reader reads the text, never the tiles, keystrokes or noise.
  • To announce a change, put TextEffect inside a live region such as role="status", as the status line above does.
  • At rest the visible text is plain text you can select and copy; the hidden copy is left out of the selection.
  • blur-in and terminal follow the reading direction, so a right-to-left line types and sharpens from the right. split-flap and roll animate each character on its own tile or reel, which breaks the joining of connected scripts such as Arabic: use blur-in or terminal there.
  • data-state on the root is idle, playing or done, and data-yj-effect names the effect.