For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. The dithercn agent skill, for building any dither on the engine, is at /.well-known/agent-skills/dithercn/SKILL.md.
dithercn

Scramble text

Text that resolves character by character through dithered blocks and glyphs, with no layout shift. Plays on mount, in view, on hover or when the text changes.

Controls
Mode
Caret
Colour
Speed1×
Stagger34ms

Installation

pnpm dlx shadcn@latest add https://dithercn.dev/r/scramble-text.json

Usage

import { ScrambleText } from "@/components/dithercn/scramble-text";
 
export function Title() {
  return <ScrambleText as="h2">Every frame accounted for</ScrambleText>;
}

Each character resolves on its own, a few milliseconds after the one before it. In the default dither mode a block in the accent colour thickens through the levels of a 4×4 Bayer matrix, a few glyphs flicker, and the letter lands, fading from the accent colour to the text colour. blocks skips the glyphs and glyphs skips the blocks.

The glyphs come from the text itself, so a headline scrambles in its own typeface instead of falling back to a terminal font. Pass characters for a specific set: SCRAMBLE_MONO and SCRAMBLE_HEX are exported.

Change children and only the characters that differ scramble, from the old text to the new one. In the demo above, the last words change every few seconds.

Every placeholder sits in the slot of the character it becomes, and the real text stays underneath, so the width never changes and nothing around it moves.

Examples

Hero reveal

mode="blocks" with a colour: each letter fills in from a sparse dither to solid before it appears. The paragraph uses a short duration and a small stagger so a long line still resolves quickly.

With trigger="hover", the text replays whenever the closest link or button is hovered or receives keyboard focus. mode="glyphs" and a short duration keep it quick enough for something people hover often.

Status that changes

trigger="change" never plays on its own; it plays when the text changes. The status label scrambles only the letters that differ and picks up the colour of each state, with a caret while it resolves. The detail line uses hex glyphs.

Props

PropTypeDefault
children*

The final text. Change it to scramble from the old text to the new one.

Type: stringDefault –
as

The element to render.

Type: "h1" | "h2" | "h3" | "h4" | "p" | "span" | "div"Default "span"
trigger

When to play. Hover also listens to the closest link or button, including keyboard focus. Change plays only when the text changes.

Type: "mount" | "in-view" | "hover" | "change"Default "in-view"
mode

How unsettled characters look: blocks then glyphs, only blocks, or only glyphs.

Type: "dither" | "blocks" | "glyphs"Default "dither"
characters

Glyphs to cycle through. SCRAMBLE_MONO and SCRAMBLE_HEX are exported.

Type: stringDefault the text's own letters
color

Colour of blocks, glyphs and the caret: a palette name or any CSS colour. Settled letters fade from it to the text colour.

Type: DitherColorDefault "ink"
duration

Milliseconds one character takes to resolve.

Type: numberDefault 520
stagger

Milliseconds between one character starting and the next.

Type: numberDefault 34
delay

Milliseconds before the first character starts.

Type: numberDefault 0
speed

Multiplies every timing. 2 plays twice as fast.

Type: numberDefault 1
rate

Glyph changes per second while a character is unsettled.

Type: numberDefault 20
caret

A caret at the leading edge while the text resolves. Always keeps it blinking after the last character.

Type: "none" | "running" | "always"Default "none"
cell

Dither cell size in CSS pixels, at least 1.

Type: numberDefault font size / 22
onComplete

Called when every character has settled.

Type: () => voidDefault –

Notes

  • The final text is always in the page as real text. While the effect plays it turns transparent and an overlay hidden from assistive technology draws the blocks and glyphs on top, so screen readers, search engines and copy and paste only ever see the final text. For a status that changes, add aria-live="polite" yourself.
  • With mount or in-view, the text waits hidden until it plays. Without JavaScript it shows normally.
  • With reduced motion turned on, the text never scrambles and the caret does not blink.
  • The overlay exists only while the effect plays (or while an always caret is shown), and the frame loop stops as soon as every character settles.