# Scramble text

> Letters decode into place.

- Type: Component (typing)
- Page: https://textlab.dev/components/scramble-text
- Markdown: https://textlab.dev/components/scramble-text.md
- Registry item: https://textlab.dev/r/scramble-text.json
- Source file: `registry/textlab/scramble-text/scramble-text.tsx`
- Dependencies: none
- Variants: random, symbols, binary
- Keywords: scramble, decrypt, decode, hacker, shuffle, cipher

## When to use

- Tech, security and launch moments.
- Short labels that should feel computed.

## When not to use

- Body copy.
- Anything that must be read during the first second.

## Installation

### CLI

```bash
npx shadcn@latest add @textlab/scramble-text
pnpm dlx shadcn@latest add @textlab/scramble-text
```

The `@textlab` name needs `"registries": { "@textlab": "https://textlab.dev/r/{name}.json" }` in `components.json`. Without it, use the full URL:

```bash
npx shadcn@latest add https://textlab.dev/r/scramble-text.json
```

Import the tokens once in your root layout: `import "@/components/textlab/tokens.css";`

### Manual

1. Install the dependencies: `npm install motion@12`
2. Copy `text-fx` (https://textlab.dev/r/text-fx.json) and this component into `components/textlab/`.

## Usage

```tsx
import { ScrambleText } from "@/components/textlab/scramble-text/scramble-text";

export function Example() {
  return <ScrambleText text="Access granted" variant="symbols" />;
}
```

## API reference

### ScrambleText

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `text` (required) | `string` | – | The copy. |
| `variant` | `"random" \| "symbols" \| "binary"` | `"random"` | Which glyphs flicker before each letter settles. |
| `charset` | `string` | – | Custom glyphs; overrides the variant. |
| `as` | `ElementType` | `"span"` | Rendered element. |
| `trigger` | `"mount" \| "inView" \| "hover" \| "manual"` | `"inView"` | What starts the animation. manual follows `active`. |
| `active` | `boolean` | `false` | Drives trigger="manual". |
| `once` | `boolean` | `true` | In view: play once instead of on every entry. |
| `delay` | `number` | `0` | Seconds before the first part moves. |
| `duration` | `number` | `1.2` | Seconds each part takes. |
| `tick` | `number` | `45` | Milliseconds between glyph changes. |
| `reducedMotion` | `"user" \| "always" \| "never"` | `"user"` | user follows the OS setting; always shows the still version. |
| `className` | `string` | – | Merged onto the root. |
| `onComplete` | `() => void` | – | Called when a one-shot animation finishes. |

## Accessibility

- Screen readers only ever hear the real text.
- The real text reserves layout, so lines never jump.

## Motion

- Settles left to right with a short lead, so the end of the word stays alive longest.
- Whitespace never scrambles, keeping word shapes.

## Reduced motion

Shows the real text, no glyphs.

## Performance

- One rAF loop while running; idle after it settles.
- Glyphs change every 45 ms, not every frame.

## Notes for AI

- Use only the props documented above. Do not invent props.
- Keep the screen-reader text and the reduced-motion branch when editing the source.
- Pick a variant from: random, symbols, binary.

## Related

- [Hover scramble](https://textlab.dev/components/hover-scramble.md): Scrambles on hover, settles on leave.
- [Matrix decode](https://textlab.dev/components/matrix-decode.md): Glyph rain resolves into the word.
- [Typewriter](https://textlab.dev/components/typewriter.md): Types, deletes and retypes with a caret.

## Also in typing

- [Text morph](https://textlab.dev/components/text-morph.md): One word turns into the next, letter by letter.
- [Word rotator](https://textlab.dev/components/word-rotator.md): Cycles words while the width follows.
- [Caret morph](https://textlab.dev/components/caret-morph.md): A caret that glides between words and reshapes to fit.

## Source

### components/textlab/scramble-text/scramble-text.tsx

```tsx
"use client";

import type { CSSProperties, ElementType } from "react";
import { SrText } from "../lib/a11y";
import { charsets, useScramble } from "../lib/scramble";
import { useReduced, type ReducedMotion } from "../lib/use-reduced";
import { useTrigger, type Trigger } from "../lib/use-trigger";

export type ScrambleTextVariant = "random" | "symbols" | "binary";

export interface ScrambleTextProps {
  text: string;
  /** Which glyphs flicker before each letter settles. */
  variant?: ScrambleTextVariant;
  /** Custom glyphs. Overrides the variant's set. */
  charset?: string;
  as?: ElementType;
  trigger?: Exclude<Trigger, "always">;
  active?: boolean;
  once?: boolean;
  delay?: number;
  /** Seconds from noise to settled. */
  duration?: number;
  /** Milliseconds between glyph changes. */
  tick?: number;
  reducedMotion?: ReducedMotion;
  className?: string;
  style?: CSSProperties;
  onComplete?: () => void;
}

/**
 * Letters decode into place, left to right. The real text holds the layout underneath, so the
 * scrambling glyphs can be any width without moving anything around them.
 */
export function ScrambleText({
  text,
  variant = "random",
  charset,
  as: Tag = "span",
  trigger = "inView",
  active,
  once = true,
  delay = 0,
  duration = 1.2,
  tick = 45,
  reducedMotion = "user",
  className,
  style,
  onComplete,
}: ScrambleTextProps) {
  const { ref, on, bind } = useTrigger<HTMLElement>({ trigger, active, once });
  const reduced = useReduced(reducedMotion);
  const frame = useScramble(text, { active: on && !reduced, duration, delay, charset: charset ?? charsets[variant], tick, mode: "sweep", idle: "scrambled", onDone: onComplete });

  if (reduced) {
    return (
      <Tag ref={ref} className={className} style={style} {...bind}>
        {text}
      </Tag>
    );
  }

  return (
    <Tag ref={ref} className={className} style={{ position: "relative", display: "inline-block", ...style }} {...bind}>
      <SrText>{text}</SrText>
      <span aria-hidden="true" style={{ visibility: "hidden" }}>
        {text}
      </span>
      <span aria-hidden="true" style={{ position: "absolute", inset: 0, whiteSpace: "pre", fontVariantLigatures: "none" }}>
        {frame}
      </span>
    </Tag>
  );
}

export default ScrambleText;
```

Full library index: https://textlab.dev/llms.txt
