ScrambleText
Reveal short text through changing random characters.
Use it for short technical strings that change: a deploy status, a commit hash, a region code, a label that reacts to hover. The noise says "computed", so keep it to a few words, and don't use it on sentences, prices or numbers people compare. For a number that moves, use Counter.
Installation
npm i @youj/motionFor 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 { ScrambleText } from '@youj/motion'
export function DeployStatus({ status }: { status: string }) {
return (
<span role="status" className="font-mono text-xs uppercase">
<ScrambleText>{status}</ScrambleText>
</span>
)
}By default it decodes when it mounts and again whenever its text changes, starting from whatever it showed last. A monospace face, or a fixed width such as w-[9ch], keeps the layout still while the glyphs roll.
Examples
Triggers
trigger takes one of mount, view, hover and change, or an array of them.
mountplays once, before the first paint.viewplays once, when 60% of the element is on screen.hoverplays when the pointer enters the element, and when the element itself takes focus.changeplays when the text changes. Without it, new text swaps in place.
hover listens on ScrambleText's own element. Render it as the control itself, with as="button", so a keyboard user who tabs to it sees the same decode as the pointer. Focus on a wrapping button or link doesn't reach a child.
Glyphs
glyphs is the alphabet the noise is drawn from. The default is upper-case letters and digits. Match it to the content, so the noise already looks like the answer: hex digits for a hash, 01 for a checksum, letters for a status.
<ScrambleText glyphs="0123456789abcdef">{commit}</ScrambleText>
<ScrambleText glyphs="01" duration="slow">SHA-256 verified</ScrambleText>Spaces stay spaces while the rest decodes, so the shape of the words reads early.
Changing text
With change alone, nothing plays on mount, and every new string decodes from the one before. Here a secret decodes out of its mask and back.
The mask is hidden from assistive technology and replaced by the word "Hidden", so a screen reader never reads out sixteen bullets.
Reference
Propschildren, as, trigger… (6)
Props
| Prop | Type | |
|---|---|---|
childrenRequired | stringTarget text. Changing it scrambles from the current text to the new one. | – |
as | ElementTypedefault 'span' | 'span' |
trigger | ScrambleTrigger | ScrambleTrigger[]When to play. default ['mount', 'change'] | ['mount', 'change'] |
glyphs | stringCharacters used while decoding. default GLYPHS.alnum | GLYPHS.alnum |
duration | DurationToken'instant''fast''base''slow''deliberate'Total time for the last character to resolve. default 'deliberate' | 'deliberate' |
tick | DurationToken'instant''fast''base''slow''deliberate'How often unresolved glyphs re-roll. default 'instant' | 'instant' |
onComplete | () => voidCalled when a decode lands on the text (at once under reduced motion). | – |
Other props pass through: extends HTMLAttributes<HTMLElement>. | ||
MotionDecode, glyph re-roll · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
Decodepositions resolve left to right with jitter; the last one lands at duration | duration.deliberate560ms | 560ms | |
Glyph re-rollunresolved positions redraw every half tick | duration.instant80ms | 80ms |
Reduced motion
The new text replaces the old at once. No glyphs.
Each position resolves at a time between 15% and 100% of duration, later for positions further right, with up to a quarter of the duration in random jitter. That reads as decoding rather than typing. Frames are written straight to the text node, so a decode never re-renders React.
Reduced motion
Text renders as it is and changes in place, instantly. Nothing decodes, on any trigger, and the status in the first preview simply reads Queued, Building, Deploying and Ready in turn.
AccessibilityKeyboard, ARIA, focus
Accessibility
- The real text is rendered once in a visually hidden span, and the decoding copy is
aria-hidden. A screen reader reads the text, never the noise. - The hidden copy updates the moment the text changes. To announce a change, put ScrambleText inside a live region, as the status in the first preview does with
role="status". hoveralso plays on keyboard focus when ScrambleText is the focusable element. Give itas="button"rather than wrapping it.- The effect is visual only. Keep it to short strings, because noise over a long string reads as broken.