# Matrix decode

> Glyph rain resolves into the word.

- Type: Component (typing)
- Page: https://textlab.dev/components/matrix-decode
- Markdown: https://textlab.dev/components/matrix-decode.md
- Registry item: https://textlab.dev/r/matrix-decode.json
- Source file: `registry/textlab/matrix-decode/matrix-decode.tsx`
- Dependencies: motion
- Variants: rain, flicker
- Keywords: matrix, code rain, decode, hacker, katakana, terminal

## When to use

- Developer tools, terminals and security products.
- Short titles on dark backgrounds.

## When not to use

- Light, calm brands; the glow reads loud.
- Paragraphs.

## Installation

### CLI

```bash
npx shadcn@latest add @textlab/matrix-decode
pnpm dlx shadcn@latest add @textlab/matrix-decode
```

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/matrix-decode.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 { MatrixDecode } from "@/components/textlab/matrix-decode/matrix-decode";

export function Example() {
  return <MatrixDecode as="h2" text="Wake up" />;
}
```

## API reference

### MatrixDecode

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `text` (required) | `string` | – | The copy. |
| `variant` | `"rain" \| "flicker"` | `"rain"` | rain drops a column of glyphs onto each letter; flicker cycles glyphs in place. |
| `glow` | `string` | `phosphor green` | Color letters flash as they lock in. |
| `charset` | `string` | `half-width katakana and digits` | Glyphs used in the rain. |
| `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.6` | Seconds each part takes. |
| `reducedMotion` | `"user" \| "always" \| "never"` | `"user"` | user follows the OS setting; always shows the still version. |
| `className` | `string` | – | Merged onto the root. |

## Accessibility

- The rain is decorative and aria-hidden; the title is read once.
- Glyph positions are seeded, so server and client render the same frame.

## Motion

- Each letter catches its column just as it lands, then cools from the glow color over 0.9 s.
- Order is loosely left to right, with seeded randomness.

## Reduced motion

The title appears in place with no rain or glow.

## Performance

- One short motion per letter; nothing runs after it resolves.

## 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: rain, flicker.

## Related

- [Scramble text](https://textlab.dev/components/scramble-text.md): Letters decode into place.
- [Neon sign](https://textlab.dev/components/neon-text.md): A neon sign that hums and flickers on.
- [Typewriter](https://textlab.dev/components/typewriter.md): Types, deletes and retypes with a caret.

## Also in typing

- [Hover scramble](https://textlab.dev/components/hover-scramble.md): Scrambles on hover, settles on leave.
- [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/matrix-decode/matrix-decode.tsx

```tsx
"use client";

import type { CSSProperties, ElementType } from "react";
import { motion } from "motion/react";
import { SrText } from "../lib/a11y";
import { hashString, seeded } from "../lib/math";
import { charsets } from "../lib/scramble";
import { splitWords } from "../lib/split";
import { useHydrated } from "../lib/use-hydrated";
import { useReduced, type ReducedMotion } from "../lib/use-reduced";
import { useTrigger, type Trigger } from "../lib/use-trigger";

export type MatrixDecodeVariant = "rain" | "flicker";

export interface MatrixDecodeProps {
  text: string;
  /** rain drops a column of glyphs onto each letter; flicker cycles glyphs in place. */
  variant?: MatrixDecodeVariant;
  /** Glow color as letters resolve. */
  glow?: string;
  charset?: string;
  as?: ElementType;
  trigger?: Exclude<Trigger, "always">;
  active?: boolean;
  once?: boolean;
  delay?: number;
  /** Seconds the whole word takes to resolve. */
  duration?: number;
  reducedMotion?: ReducedMotion;
  className?: string;
  style?: CSSProperties;
}

/**
 * Glyph rain resolves into the word. Each letter catches a short falling column of characters,
 * flashes bright as it locks, then cools to the text color.
 */
export function MatrixDecode({
  text,
  variant = "rain",
  glow = "oklch(0.86 0.2 150)",
  charset = charsets.katakana,
  as: Tag = "span",
  trigger = "inView",
  active,
  once = true,
  delay = 0,
  duration = 1.6,
  reducedMotion = "user",
  className,
  style,
}: MatrixDecodeProps) {
  const { ref, on, bind } = useTrigger<HTMLElement>({ trigger, active, once });
  const hydrated = useHydrated();
  const reduced = useReduced(reducedMotion);
  const words = splitWords(text);
  const rand = seeded(hashString(text));
  const total = words.reduce((n, w) => n + w.chars.length, 0);

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

  const glyph = () => charset[Math.floor(rand() * charset.length)];

  return (
    <Tag ref={ref} className={className} style={style} data-tl-motion={reducedMotion === "user" ? "user" : undefined} data-tl-pending={hydrated ? undefined : ""} {...bind}>
      <SrText>{text}</SrText>
      <span aria-hidden="true">
        {words.map((word, w) => (
          <span key={w}>
            <span style={{ display: "inline-block", whiteSpace: "nowrap" }}>
              {word.chars.map((char, c) => {
                const i = word.charOffset + c;
                // Letters resolve in a loose left-to-right order with a little randomness.
                const at = delay + (i / Math.max(total, 1)) * duration * 0.7 + rand() * duration * 0.25;
                const column = Array.from({ length: 6 }, glyph);
                const flicker = Array.from({ length: 4 }, glyph);
                return (
                  <span key={c} style={{ position: "relative", display: "inline-block" }}>
                    {variant === "rain" ? (
                      <motion.span
                        style={{ position: "absolute", left: 0, right: 0, bottom: "100%", display: "flex", flexDirection: "column", alignItems: "center", lineHeight: 1, fontSize: "0.5em", color: glow, pointerEvents: "none" }}
                        initial={{ transform: "translateY(-60%)", opacity: 0 }}
                        animate={on ? { transform: ["translateY(-60%)", "translateY(140%)"], opacity: [0, 1, 0] } : { transform: "translateY(-60%)", opacity: 0 }}
                        transition={on ? { duration: 0.55, delay: Math.max(0, at - 0.45), ease: "easeIn" } : { duration: 0 }} // glyphs fall, so they accelerate
                      >
                        {column.map((g, k) => (
                          <span key={k} style={{ opacity: (k + 1) / column.length }}>
                            {g}
                          </span>
                        ))}
                      </motion.span>
                    ) : (
                      <motion.span
                        style={{ position: "absolute", inset: 0, textAlign: "center", color: glow }}
                        initial={{ opacity: 0 }}
                        animate={on ? { opacity: [0, 1, 1, 0] } : { opacity: 0 }}
                        transition={on ? { duration: 0.5, delay: Math.max(0, at - 0.4), times: [0, 0.1, 0.8, 1] } : { duration: 0 }}
                      >
                        {flicker.map((g, k) => (
                          <motion.span
                            key={k}
                            style={{ position: "absolute", inset: 0 }}
                            initial={{ opacity: 0 }}
                            animate={on ? { opacity: [0, 1, 0] } : { opacity: 0 }}
                            transition={on ? { duration: 0.14, delay: Math.max(0, at - 0.4) + k * 0.1 } : { duration: 0 }}
                          >
                            {g}
                          </motion.span>
                        ))}
                      </motion.span>
                    )}
                    <motion.span
                      data-tl-part=""
                      style={{ display: "inline-block" }}
                      initial={{ opacity: 0 }}
                      animate={{ opacity: on ? 1 : 0 }}
                      transition={on ? { duration: 0.08, delay: at } : { duration: 0.15 }}
                    >
                      {char}
                    </motion.span>
                    {/* A glowing copy flashes on top as the letter locks, then cools away. */}
                    <motion.span
                      style={{ position: "absolute", inset: 0, color: glow, textShadow: `0 0 0.35em ${glow}`, pointerEvents: "none" }}
                      initial={{ opacity: 0 }}
                      animate={on ? { opacity: [0, 1, 0] } : { opacity: 0 }}
                      transition={on ? { duration: 1, delay: at, times: [0, 0.08, 1], ease: "easeOut" } : { duration: 0 }}
                    >
                      {char}
                    </motion.span>
                  </span>
                );
              })}
            </span>
            {w < words.length - 1 ? " " : null}
          </span>
        ))}
      </span>
    </Tag>
  );
}

export default MatrixDecode;
```

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