Cipher Text Reveal
Text that decodes itself: characters reveal left to right while a short window of cipher glyphs churns just ahead of the reveal edge, like a lock finding its combination. One interval drives reveal, tail resolution, and completion; the full text stays in an sr-only span for screen readers, and a ref handle exposes start/reset for sequencing lines.
TextReactTailwind CSS
CSSTailwind
Manual
Create a file and paste the following code into it.
cipher-text-reveal.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
"use client";
import { type Ref, useCallback, useEffect, useImperativeHandle, useState } from "react";
/* ------------------------------------------------------------------ */
/* CipherTextReveal */
/* ------------------------------------------------------------------ */
interface CipherTextRevealProps {
text: string;
/** ms between reveal ticks [Optional, default: 50] */
speed?: number;
/** Scrambled chars running ahead of the revealed text [Optional, default: 2] */
cipherCount?: number;
/** Glyph pool for the scramble [Optional] */
characters?: string;
className?: string;
/** Class for the still-scrambled portion [Optional] */
cipherClassName?: string;
/** Start on mount [Optional, default: true] */
autoStart?: boolean;
delay?: number;
onComplete?: () => void;
ref?: Ref<CipherTextRevealHandle>;
}
export interface CipherTextRevealHandle {
start: () => void;
reset: () => void;
}
/**
* Text that decodes itself: characters reveal left to right while a short
* window of cipher glyphs churns just ahead of the reveal edge, like a lock
* finding its combination. Exposes start/reset through a ref handle and
* keeps the full text in an sr-only span for screen readers.
* @param {string} text - Final text [Required]
* @param {number} cipherCount - Churning glyphs ahead of the edge [Optional, default: 2]
*/
export function CipherTextReveal({
text,
speed = 50,
cipherCount = 2,
characters = "abcdefghijklmnopqrstuvwxyz!@#$%^&*()_+",
className = "",
cipherClassName = "",
autoStart = true,
delay = 0,
onComplete,
ref,
}: CipherTextRevealProps) {
const [display, setDisplay] = useState("");
const [running, setRunning] = useState(false);
const [visible, setVisible] = useState(0);
const [tailTick, setTailTick] = useState(0);
const start = useCallback(() => {
setRunning(true);
setVisible(0);
setTailTick(0);
}, []);
const reset = useCallback(() => {
setRunning(false);
setVisible(0);
setTailTick(0);
setDisplay("");
}, []);
useImperativeHandle(ref, () => ({ start, reset }), [start, reset]);
useEffect(() => {
if (!autoStart) return;
if (delay > 0) {
const t = setTimeout(start, delay);
return () => clearTimeout(t);
}
start();
}, [autoStart, delay, start]);
useEffect(() => {
if (!running) return;
const interval = setInterval(() => {
if (visible < text.length) {
setVisible((v) => v + 1);
} else if (tailTick < cipherCount) {
setTailTick((t) => t + 1);
} else {
clearInterval(interval);
setRunning(false);
onComplete?.();
}
const remaining = Math.max(0, text.length - visible);
const churn = Math.min(remaining, cipherCount);
const cipher = Array.from(
{ length: churn },
() => characters[Math.floor(Math.random() * characters.length)],
).join("");
setDisplay(text.slice(0, visible) + cipher);
}, speed);
return () => clearInterval(interval);
}, [running, text, visible, tailTick, cipherCount, characters, speed, onComplete]);
const revealed = display.slice(0, visible);
const cipher = display.slice(visible);
return (
<>
<span className="sr-only">{text}</span>
<span className="inline-block whitespace-pre-wrap" aria-hidden="true">
<span className={className}>{revealed}</span>
<span className={cipherClassName}>{cipher}</span>
</span>
</>
);
}
Update the import paths to match your project setup.
Similar components
Install via CLI
Resource details
PublishedJuly 17, 2026
CategoryText
ReactTailwind CSS