Text scramble
Characters cycle through glyphs and settle into the final text.
$
pnpm dlx shadcn@latest add https://lodeui.com/r/text-scramble.jsonHover the key to decode it again
components/examples/text-scramble-demo.tsx
"use client";
import { TextScramble } from "@/components/ui/text-scramble";
export default function TextScrambleDemo() {
return (
<div className="flex flex-col items-center gap-4 text-center">
<TextScramble text="DEPLOYMENT READY" className="text-3xl font-semibold" />
<TextScramble text="sk_live_4f9a••••••••" trigger="hover" className="text-sm text-muted-foreground" />
<p className="text-xs text-muted-foreground">Hover the key to decode it again</p>
</div>
);
}When to use
- Short technical labels and headings that decode on load or hover.
When not to use
- Sentences and body text.
Installation
$
pnpm dlx shadcn@latest add https://lodeui.com/r/text-scramble.json1Install the dependencies
npm install motion2Copy the source into your project
components/ui/text-scramble.tsx"use client"; import * as React from "react"; import { useReducedMotion } from "motion/react"; import { cn } from "@/lib/utils"; const GLYPHS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&*+=<>/"; export interface TextScrambleProps { text: string; /** ms for the whole decode. */ duration?: number; /** Re-scramble on hover. */ trigger?: "mount" | "hover" | "both"; className?: string; } /** Characters cycle through glyphs and settle left to right into the final text. */ export function TextScramble({ text, duration = 900, trigger = "both", className }: TextScrambleProps) { const reduce = useReducedMotion(); const [out, setOut] = React.useState(text); const frame = React.useRef(0); const run = React.useCallback(() => { if (reduce) return; cancelAnimationFrame(frame.current); const start = performance.now(); const tick = (now: number) => { const p = Math.min(1, (now - start) / duration); const settled = Math.floor(p * text.length); setOut( [...text] .map((ch, i) => (i < settled || ch === " " ? ch : GLYPHS[Math.floor(Math.random() * GLYPHS.length)])) .join(""), ); if (p < 1) frame.current = requestAnimationFrame(tick); }; frame.current = requestAnimationFrame(tick); }, [text, duration, reduce]); React.useEffect(() => { if (trigger !== "hover") run(); return () => cancelAnimationFrame(frame.current); }, [run, trigger]); return ( <span className={cn("font-mono tabular-nums", className)} onPointerEnter={trigger !== "mount" ? run : undefined} aria-label={text}> <span aria-hidden>{out}</span> </span> ); }
Usage
Import it from @/components/ui/text-scramble after installing.
example.tsx
import { TextScramble } from "@/components/ui/text-scramble";
export function Label() {
return <TextScramble text="DEPLOY_READY" className="font-mono" />;
}API reference
TextScramble
Characters cycle through glyphs and settle left to right.
| Prop | Type | Default | Description |
|---|---|---|---|
| text | string | – | Final text. |
| duration | number | 900 | ms for the decode. |
| trigger | "mount" | "hover" | "both" | "both" | When it plays. |
Accessibility
- aria-label holds the real text; glyphs are aria-hidden.
- Static under reduced motion.
Motion
- Decodes over the duration.
Responsive behavior
- Use a monospace font so the width doesn't jump.
Performance
- requestAnimationFrame only while playing.
Notes for AI
- Keep text under ~24 characters.
This page as Markdown, for assistants and coding agents:
/components/text-scramble/markdownThe full library index for assistants is at /llms.txt.
Lode222 doc pagesllms.txt