Text Scramble Reveal
Real text stays as live DOM underneath; an aria-hidden cipher overlay ticks per-character then clears via clip-path. Three variants: a binary terminal decipher, a mixed-symbol search settle, and a slower hex editorial cipher.
Real text stays as live DOM underneath; an aria-hidden cipher overlay ticks per-character then clears via clip-path. Three variants: a binary terminal decipher, a mixed-symbol search settle, and a slower hex editorial cipher.
Decipher reveal · steps cadence
A scramble overlay ticks through symbol-like glyphs while the real destination phrase remains present, selectable, and screen-reader-readable underneath. Three variants demonstrate a terminal-style binary decipher, a mixed-symbol search result settle, and a hex editorial cipher.
Mono · binary cipher
A monospace terminal-style decipher pass. The noise overlay cycles through binary glyphs (0/1) before clearing to reveal the destination phrase. Steps timing on the overlay; the underlying phrase is real DOM text the whole time.
Mixed glyphs · quick settle
A shorter mixed-symbol scramble for search results — makes the result feel processed without delaying comprehension. The destination phrase is visible enough underneath that the effect does not block reading.
Hex · slow editorial reveal
A slower editorial cipher using hex glyphs (0-9 / A-F). Lets the cipher layer feel intentional without becoming unreadable; the phrase stays moderate length so scramble noise doesn't become visual clutter.
A CSS text scramble reveal keeps the final phrase as real text, overlays aria-hidden cipher cells, then clears that overlay with a stepped clip-path mask.
<p class="scramble-word">2<span class="scramble-word__real">ACCESS GRANTED</span>3<span class="scramble-word__noise" aria-hidden="true"><span style="--i:0">A</span><span style="--i:1">C</span><span style="--i:2">C</span><span style="--i:3">E</span><span style="--i:4">S</span><span style="--i:5">S</span><span style="--i:6"> </span><span style="--i:7">G</span><span style="--i:8">R</span><span style="--i:9">A</span><span style="--i:10">N</span><span style="--i:11">T</span><span style="--i:12">E</span><span style="--i:13">D</span></span></p><style>.scramble-word {position: relative;display: inline-block;margin: 0;color: #dcfce7;font: 800 1.8rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;letter-spacing: .05em;}.scramble-word__real {position: relative;z-index: 1;}.scramble-word__noise {position: absolute;29inset: 0;z-index: 2;display: inline-flex;color: #34d399;text-shadow: 0 0 14px rgba(52, 211, 153, .45);pointer-events: none;clip-path: inset(0 0 0 0);animation: text-scramble-recipe-clear 2.4s steps(14, end) infinite;37}.scramble-word__noise span {display: inline-block;animation: text-scramble-recipe-tick .42s steps(2, end) infinite;41animation-delay: calc(var(--i) * -48ms);}@keyframes text-scramble-recipe-clear {0%, 52% { opacity: .88; clip-path: inset(0 0 0 0); }100% { opacity: 0; clip-path: inset(0 0 0 100%); }}@keyframes text-scramble-recipe-tick {50% { transform: translateY(-.05em); }}50@media (prefers-reduced-motion: reduce) {.scramble-word__noise,.scramble-word__noise span {animation: none;opacity: 0;}}</style>
Render the destination phrase as normal DOM text and mark the animated noise layer aria-hidden. Do not replace the text content itself every frame unless you also manage screen reader announcements carefully.
Render the destination phrase as normal DOM text and mark the animated noise layer aria-hidden. Do not replace the text content itself every frame unless you also manage screen reader announcements carefully.
Both reveals run the same clip-path inset(0 0 0 0) → inset(0 0 0 100%) keyframe over 1.8s, so the right-edge cut moves across the same distance. ease-in-out timing slides the cut smoothly — the result reads as a dissolve. steps(6, end) snaps the cut between six discrete positions so the phrase clears in chunks, like a terminal settling on the decoded text.
Scramble effects read as discrete decipher frames. steps() creates hard frame changes for the mask and per-cell ticks, while smooth easing reads like a normal fade or slide.
Same six cells, same per-cell tick animation. Without per-cell animation-delay the whole row jumps in unison every cycle — it reads as one blinking word. With staggered negative delays (--i * -50ms) each cell hits its peak at a different time, so the row ripples and looks like cells settling independently — what makes the scramble feel "alive".
Scramble effects read as discrete decipher frames. steps() creates hard frame changes for the mask and per-cell ticks, while smooth easing reads like a normal fade or slide.
Hide or freeze the cipher overlay and show the completed phrase. Scramble motion is high-frequency visual noise, so reduced-motion users should not have to wait through the reveal to read the text.
Same 1.8s cycle, same per-cell stagger (var(--i) negative delay), same green decoder palette. BEFORE wiggles a fixed letter in each cell — the content never changes so the cipher reads as "two letters bouncing". AFTER stacks 4 glyphs vertically inside each cell's 1em viewport; an explicit keyframe holds each letter for ~400ms, then snaps the column up by 1em (54ms transition) to expose the next letter — three visible roll-snaps until the final glyph lands. Clean single-letter visibility at rest, blink-fast snap between. Both cycles end at the same moment.
The base recipe wiggles each cell vertically — visually busy but the cell content never changes, so the cipher does not actually look like decoding. Production "decoder" effects scroll each cell through a vertical strip of glyphs, snapping to a new letter on every step until the final character lands. The overlay clip-path still clears the whole cipher at the end of the cycle. Same overlay choreography, same steps()-driven cadence, same per-cell stagger via --i — what changes is that each cell now exposes a different glyph on every tick instead of jiggling the same one.
Paste this as a complete alternative to the base recipe — each cipher cell is now a vertical letter column (5 glyphs) scrubbed by translateY steps(5, end), so the content visibly cycles before the overlay clip-path clears.
<p class="decode-word" aria-label="ACCESS GRANTED"><span class="decode-word__real">ACCESS GRANTED</span><span class="decode-word__cipher" aria-hidden="true"><span class="decode-word__cell" style="--i:0"><i>X</i><i>Q</i><i>7</i><i>%</i><i>A</i></span><span class="decode-word__cell" style="--i:1"><i>P</i><i>3</i><i>K</i><i>R</i><i>C</i></span><span class="decode-word__cell" style="--i:2"><i>9</i><i>F</i><i>L</i><i>W</i><i>C</i></span><span class="decode-word__cell" style="--i:3"><i>$</i><i>D</i><i>B</i><i>N</i><i>E</i></span><span class="decode-word__cell" style="--i:4"><i>4</i><i>M</i><i>V</i><i>T</i><i>S</i></span><span class="decode-word__cell" style="--i:5"><i>Z</i><i>8</i><i>Y</i><i>J</i><i>S</i></span><span class="decode-word__cell" style="--i:6"><i> </i><i> </i><i> </i><i> </i><i> </i></span><span class="decode-word__cell" style="--i:7"><i>U</i><i>6</i><i>H</i><i>!</i><i>G</i></span><span class="decode-word__cell" style="--i:8"><i>K</i><i>2</i><i>O</i><i>+</i><i>R</i></span><span class="decode-word__cell" style="--i:9"><i>5</i><i>W</i><i>X</i><i>?</i><i>A</i></span><span class="decode-word__cell" style="--i:10"><i>B</i><i>1</i><i>I</i><i>=</i><i>N</i></span><span class="decode-word__cell" style="--i:11"><i>F</i><i>0</i><i>P</i><i>*</i><i>T</i></span><span class="decode-word__cell" style="--i:12"><i>L</i><i>Y</i><i>Q</i><i>@</i><i>E</i></span><span class="decode-word__cell" style="--i:13"><i>V</i><i>S</i><i>U</i><i>#</i><i>D</i></span></span></p><style>.decode-word {position: relative;display: inline-block;margin: 0;color: #dcfce7;font: 800 1.8rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;letter-spacing: .05em;}.decode-word__real { position: relative; z-index: 1; }.decode-word__cipher {position: absolute;inset: 0;z-index: 2;display: inline-flex;color: #34d399;text-shadow: 0 0 14px rgba(52, 211, 153, .45);pointer-events: none;clip-path: inset(0 0 0 0);animation: decode-clear 2.4s steps(14, end) infinite;}.decode-word__cell {display: inline-flex;flex-direction: column;height: 1em;overflow: hidden;font-weight: 800;animation: decode-scroll .42s steps(5, end) infinite;animation-delay: calc(var(--i) * -48ms);}.decode-word__cell i {display: block;height: 1em;line-height: 1;font-style: normal;}@keyframes decode-scroll {from { transform: translateY(0); }to { transform: translateY(-4em); }}@keyframes decode-clear {0%, 52% { opacity: .88; clip-path: inset(0 0 0 0); }100% { opacity: 0; clip-path: inset(0 0 0 100%); }}@media (prefers-reduced-motion: reduce) {.decode-word__cipher,.decode-word__cell { animation: none; opacity: 0; }}</style>
Decipher-style text reveal where the destination phrase stays as live DOM text and a decorative aria-hidden cipher overlay ticks through symbol-like glyphs then clears via clip-path. The base text is selectable + copyable + readable throughout; only the overlay animates.
Reach for scramble reveals on terminal-style product surfaces, decoding metaphors (search results, AI inference), and hero string entrances. Skip it for utility chrome — the cipher pattern is loud enough to feel marketing-y. Skip it for content over 30 characters; the per-cell tick budget becomes too long.
Two stacked DOM layers: a real text node holding the destination string, and an aria-hidden overlay positioned absolutely on top. The overlay swaps characters every ~30–60ms from a glyph pool (Katakana, custom symbols) via requestAnimationFrame or a timed JS loop. The overlay clears left-to-right via clip-path: inset(0 100% 0 0) animating to inset(0 0 0 0) over the reveal duration. Once the clip clears, the overlay text matches the underlying text and you can remove the overlay (or just leave it — the user sees real text only).
The clip-path direction must travel from right to left from the user’s perspective so the cipher “decodes” toward final state — reversing the inset direction breaks the metaphor. The cipher glyph pool affects the “feel”: pure ASCII reads flat, while mixing Katakana + symbols reads Matrix-coded. Avoid Emoji in the pool — they have variable widths and the cipher overlay jitters. Set font-variant-numeric: tabular-nums on the overlay if the destination contains digits, or the swap animation reflows.
The decorative overlay must be aria-hidden="true" so screen readers ignore the cipher glyphs. The underlying real text is announced normally on page load. Under prefers-reduced-motion: reduce skip the cipher swap and clip-path animation entirely — the real text is already there; no transformation needed. Verify focus order if any controls live inside the scrambled region.
Scramble effects should separate readable content from decorative decoys. Keep the final string in the DOM, use aria-hidden for transient character noise, and end quickly enough that the user is not waiting for text to become useful.
The risk is localization. Grapheme clusters, accents, and non-Latin scripts break naive per-character loops. Use a segmentation strategy or limit scrambling to decorative labels that have a stable accessible name.