Text Scramble Effect for React

Text that decrypts character by character with a scramble effect. Trigger on scroll, hover, or mount.

Component

Installation

 

Usage

Import

Add the TextScramble import.

import { TextScramble } from "@/components/text-scramble";

Use

Use with text and trigger mode.

<TextScramble text="Your text" trigger="inView" />;

Guidelines

  • Use trigger='inView' for hero headings that animate when scrolled into view.
  • Use trigger='hover' for interactive reveals.
  • scrambleSpeed: delay in ms between characters (0 = fastest).
  • Customize chars for different scramble character sets (e.g. '01' for binary).

Props

All props are optional unless marked required. Use these to customize every aspect of the component.

PropTypeDefaultDescription
textstring-The text to scramble and reveal.
charsstring"!@#$%^&*()_+-=[]{}|;:,.<>?"Characters to cycle through during scramble.
trigger"inView" | "mount" | "hover""inView"When to trigger: "inView" (scroll), "mount", or "hover".
scrambleSpeednumber0Delay in ms between characters. 0 = fastest (requestAnimationFrame).
cursorClassNamestring-Classes for the blinking cursor during scramble.
inViewThresholdnumber0.3IntersectionObserver threshold (0–1) for inView trigger.
classNamestring-Additional CSS classes.
onComplete() => void-Callback when scramble completes.

Accessibility

  • The hover trigger only responds to onMouseEnter and onMouseLeave, so keyboard and touch users cannot start the reveal; add focus and keypress handlers or make the wrapper focusable for parity.
  • The wrapper for the hover variant is a non-focusable div with role group and no accessible label, so it is not reachable or operable via the keyboard.
  • The text is rendered as raw mutating content with no aria-label of the final string and no aria-live region, so assistive tech may announce intermediate scrambled characters; expose the final text to screen readers.
  • The blinking cursor span is correctly marked aria-hidden so it is not announced.
  • The component does not check prefers-reduced-motion, so the scramble runs for everyone; it should be guarded to render the final text immediately for users who prefer reduced motion.

Performance

  • Animation mutates the span text content via setState on every step rather than animating transform or opacity, so each frame triggers layout and repaint instead of a cheap compositor-only update.
  • Stepping is driven by window.setTimeout (not requestAnimationFrame despite the prop copy), and the timer is cleared on cleanup to avoid leaks.
  • The inView trigger uses an IntersectionObserver that is disconnected on unmount, and hasAnimatedRef gates it to run only once so it does not re-scramble on repeated scroll.
  • The blinking cursor relies on the CSS animate-pulse opacity animation, which is GPU-friendly, but no will-change or compositing hint is set on the animated text itself.
  • There is no reduced-motion or visibility gating beyond the inView observer, so a long string at a high scrambleSpeed keeps scheduling timers and re-rendering for its full duration.

Examples

On scroll (inView)

Scramble animates when the text enters the viewport.

import { TextScramble } from "@/components/text-scramble";

<TextScramble
  text="Build Something Beautiful"
  trigger="inView"
/>;

On mount

Scramble runs immediately on mount.

import { TextScramble } from "@/components/text-scramble";

<TextScramble text="Hello World" trigger="mount" />;

On hover

Scramble runs when the user hovers over the text.

import { TextScramble } from "@/components/text-scramble";

<span className="text-2xl">
  <TextScramble text="Hover to reveal" trigger="hover" />
</span>;

Speed & chars

50ms delay per character, binary chars, custom cursor color.

import { TextScramble } from "@/components/text-scramble";

<TextScramble
  text="Slower reveal"
  scrambleSpeed={50}
  chars="01"
  cursorClassName="text-amber-500"
/>;

Threshold & callback

Triggers when 50% visible; onComplete callback.

import { TextScramble } from "@/components/text-scramble";

<TextScramble
  text="Custom threshold"
  inViewThreshold={0.5}
  onComplete={() => console.log("Done!")}
/>;

Last updated on Jun 19

Made with ❤️ by Pulkit &

© 2026 Pulkit. All rights reserved

Last updated: