# 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.

> For the complete documentation index, see [llms.txt](/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](/.well-known/agent-skills/dithercn/SKILL.md).

<ComponentPreview src="examples/scramble-text-demo.tsx" size="lg" controls="scrambleControls">
  <ScrambleTextDemo />
</ComponentPreview>

## Installation [#installation]

<InstallBlock name="scramble-text" />

## Usage [#usage]

```tsx
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 [#examples]

### Hero reveal [#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.

<ComponentPreview src="examples/scramble-text-hero.tsx" size="lg">
  <ScrambleTextHero />
</ComponentPreview>

### Nav links on hover [#nav-links-on-hover]

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.

<ComponentPreview src="examples/scramble-text-hover.tsx">
  <ScrambleTextHover />
</ComponentPreview>

### Status that changes [#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.

<ComponentPreview src="examples/scramble-text-status.tsx">
  <ScrambleTextStatus />
</ComponentPreview>

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `string` |  | The final text. Change it to scramble from the old text to the new one. |
| `as` | `"h1" \| "h2" \| "h3" \| "h4" \| "p" \| "span" \| "div"` | `"span"` | The element to render. |
| `trigger` | `"mount" \| "in-view" \| "hover" \| "change"` | `"in-view"` | When to play. Hover also listens to the closest link or button, including keyboard focus. Change plays only when the text changes. |
| `mode` | `"dither" \| "blocks" \| "glyphs"` | `"dither"` | How unsettled characters look: blocks then glyphs, only blocks, or only glyphs. |
| `characters` | `string` | `the text's own letters` | Glyphs to cycle through. SCRAMBLE_MONO and SCRAMBLE_HEX are exported. |
| `color` | `DitherColor` | `"ink"` | Colour of blocks, glyphs and the caret: a palette name or any CSS colour. Settled letters fade from it to the text colour. |
| `duration` | `number` | `520` | Milliseconds one character takes to resolve. |
| `stagger` | `number` | `34` | Milliseconds between one character starting and the next. |
| `delay` | `number` | `0` | Milliseconds before the first character starts. |
| `speed` | `number` | `1` | Multiplies every timing. 2 plays twice as fast. |
| `rate` | `number` | `20` | Glyph changes per second while a character is unsettled. |
| `caret` | `"none" \| "running" \| "always"` | `"none"` | A caret at the leading edge while the text resolves. Always keeps it blinking after the last character. |
| `cell` | `number` | `font size / 22` | Dither cell size in CSS pixels, at least 1. |
| `onComplete` | `() => void` |  | Called when every character has settled. |

## Notes [#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.