14 CSS Typewriter Effect Designs 11 / 14
CSS Typewriter Matrix Scramble Decode
Random characters scramble across the target string before each letter snaps to its final value — a hacker-movie decode effect powered by JS with CSS glow styling.
The code
<div class="tw-11">
<div class="tw-11__screen">
<div class="tw-11__scan"></div>
<p class="tw-11__status">DECRYPTING PAYLOAD</p>
<div class="tw-11__output" id="tw-11-output" aria-label="ACCESS GRANTED" aria-live="off"></div>
<button class="tw-11__btn" id="tw-11-run">▶ DECODE</button>
</div>
</div> <div class="tw-11">
<div class="tw-11__screen">
<div class="tw-11__scan"></div>
<p class="tw-11__status">DECRYPTING PAYLOAD</p>
<div class="tw-11__output" id="tw-11-output" aria-label="ACCESS GRANTED" aria-live="off"></div>
<button class="tw-11__btn" id="tw-11-run">▶ DECODE</button>
</div>
</div>.tw-11, .tw-11 *, .tw-11 *::before, .tw-11 *::after { box-sizing: border-box; margin: 0; padding: 0; }
.tw-11 ::selection { background: #00ff41; color: #000; }
.tw-11 {
--green: #00ff41;
--dim: #003d10;
--bg: #020a03;
font-family: 'Courier New', monospace;
min-height: 340px;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 24px;
}
.tw-11__screen {
width: 100%;
max-width: 480px;
border: 1px solid #0a2a0a;
border-radius: 8px;
padding: 32px 24px;
position: relative;
overflow: hidden;
background: #030c03;
box-shadow: 0 0 40px rgba(0,255,65,0.08), inset 0 0 40px rgba(0,0,0,0.5);
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 20px;
}
.tw-11__scan {
position: absolute;
inset: 0;
background: repeating-linear-gradient(
0deg, transparent, transparent 2px, rgba(0,255,65,0.03) 2px, rgba(0,255,65,0.03) 4px
);
pointer-events: none;
}
.tw-11__status {
font-size: 0.7rem;
letter-spacing: 0.2em;
color: var(--dim);
}
.tw-11__output {
font-size: clamp(1.4rem, 4vw, 2rem);
font-weight: 700;
color: var(--green);
text-shadow: 0 0 10px rgba(0,255,65,0.6);
letter-spacing: 0.08em;
min-height: 1.4em;
display: flex;
flex-wrap: wrap;
gap: 0;
}
.tw-11__output .tw-11-char {
display: inline-block;
color: var(--dim);
text-shadow: none;
transition: color 0.1s, text-shadow 0.1s;
}
.tw-11__output .tw-11-char.locked {
color: var(--green);
text-shadow: 0 0 12px rgba(0,255,65,0.8);
}
.tw-11__btn {
background: transparent;
border: 1px solid var(--dim);
color: var(--green);
font-family: inherit;
font-size: 0.78rem;
letter-spacing: 0.15em;
padding: 8px 18px;
cursor: pointer;
border-radius: 4px;
transition: border-color 0.2s, box-shadow 0.2s;
}
.tw-11__btn:hover {
border-color: var(--green);
box-shadow: 0 0 12px rgba(0,255,65,0.2);
}
@media (prefers-reduced-motion: reduce) {
.tw-11__output .tw-11-char { transition: none; }
} .tw-11, .tw-11 *, .tw-11 *::before, .tw-11 *::after { box-sizing: border-box; margin: 0; padding: 0; }
.tw-11 ::selection { background: #00ff41; color: #000; }
.tw-11 {
--green: #00ff41;
--dim: #003d10;
--bg: #020a03;
font-family: 'Courier New', monospace;
min-height: 340px;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 24px;
}
.tw-11__screen {
width: 100%;
max-width: 480px;
border: 1px solid #0a2a0a;
border-radius: 8px;
padding: 32px 24px;
position: relative;
overflow: hidden;
background: #030c03;
box-shadow: 0 0 40px rgba(0,255,65,0.08), inset 0 0 40px rgba(0,0,0,0.5);
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 20px;
}
.tw-11__scan {
position: absolute;
inset: 0;
background: repeating-linear-gradient(
0deg, transparent, transparent 2px, rgba(0,255,65,0.03) 2px, rgba(0,255,65,0.03) 4px
);
pointer-events: none;
}
.tw-11__status {
font-size: 0.7rem;
letter-spacing: 0.2em;
color: var(--dim);
}
.tw-11__output {
font-size: clamp(1.4rem, 4vw, 2rem);
font-weight: 700;
color: var(--green);
text-shadow: 0 0 10px rgba(0,255,65,0.6);
letter-spacing: 0.08em;
min-height: 1.4em;
display: flex;
flex-wrap: wrap;
gap: 0;
}
.tw-11__output .tw-11-char {
display: inline-block;
color: var(--dim);
text-shadow: none;
transition: color 0.1s, text-shadow 0.1s;
}
.tw-11__output .tw-11-char.locked {
color: var(--green);
text-shadow: 0 0 12px rgba(0,255,65,0.8);
}
.tw-11__btn {
background: transparent;
border: 1px solid var(--dim);
color: var(--green);
font-family: inherit;
font-size: 0.78rem;
letter-spacing: 0.15em;
padding: 8px 18px;
cursor: pointer;
border-radius: 4px;
transition: border-color 0.2s, box-shadow 0.2s;
}
.tw-11__btn:hover {
border-color: var(--green);
box-shadow: 0 0 12px rgba(0,255,65,0.2);
}
@media (prefers-reduced-motion: reduce) {
.tw-11__output .tw-11-char { transition: none; }
}(function() {
const container = document.getElementById('tw-11-output');
const btn = document.getElementById('tw-11-run');
if (!container) return;
const TARGET = 'ACCESS GRANTED';
const NOISE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*<>?/|';
const SCRAMBLE_FRAMES = 10;
const CHAR_DELAY = 60;
let intervals = [];
function rand(str) {
return str[Math.floor(Math.random() * str.length)];
}
function run() {
intervals.forEach(clearInterval);
intervals = [];
container.innerHTML = '';
const spans = TARGET.split('').map((ch) => {
const span = document.createElement('span');
span.className = 'tw-11-char';
span.textContent = ch === ' ' ? ' ' : rand(NOISE);
container.appendChild(span);
return { span, final: ch };
});
spans.forEach(({ span, final }, i) => {
if (final === ' ') { span.textContent = ' '; return; }
let frames = 0;
const startDelay = i * CHAR_DELAY;
setTimeout(() => {
const iv = setInterval(() => {
if (frames >= SCRAMBLE_FRAMES) {
clearInterval(iv);
span.textContent = final;
span.classList.add('locked');
} else {
span.textContent = rand(NOISE);
frames++;
}
}, 40);
intervals.push(iv);
}, startDelay);
});
}
btn.addEventListener('click', run);
run();
})(); (function() {
const container = document.getElementById('tw-11-output');
const btn = document.getElementById('tw-11-run');
if (!container) return;
const TARGET = 'ACCESS GRANTED';
const NOISE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*<>?/|';
const SCRAMBLE_FRAMES = 10;
const CHAR_DELAY = 60;
let intervals = [];
function rand(str) {
return str[Math.floor(Math.random() * str.length)];
}
function run() {
intervals.forEach(clearInterval);
intervals = [];
container.innerHTML = '';
const spans = TARGET.split('').map((ch) => {
const span = document.createElement('span');
span.className = 'tw-11-char';
span.textContent = ch === ' ' ? ' ' : rand(NOISE);
container.appendChild(span);
return { span, final: ch };
});
spans.forEach(({ span, final }, i) => {
if (final === ' ') { span.textContent = ' '; return; }
let frames = 0;
const startDelay = i * CHAR_DELAY;
setTimeout(() => {
const iv = setInterval(() => {
if (frames >= SCRAMBLE_FRAMES) {
clearInterval(iv);
span.textContent = final;
span.classList.add('locked');
} else {
span.textContent = rand(NOISE);
frames++;
}
}, 40);
intervals.push(iv);
}, startDelay);
});
}
btn.addEventListener('click', run);
run();
})();How this works
JS splits the target string into character slots. For each slot, a rapid setInterval replaces the character with a random glyph from a noise alphabet (Latin, symbols, Katakana) for a configurable number of iterations. After those scramble frames complete, the interval is cleared and the correct character is written. Each slot starts its scramble on a staggered setTimeout proportional to its index, creating a left-to-right cascade decode.
Every character slot is a <span> with a CSS custom property that the JS sets to a value between 0 and 1 (progress). CSS maps this progress to a color lerp from dim-cyan to bright-white using color-mix(), and the text-shadow intensity scales accordingly — so scrambling characters glow dimly and locked characters glow brightly.
Customize
- Swap the noise alphabet from Latin symbols to Katakana only (
const CHARS = "アイウエオカキクケコ...") for a more cinematic Matrix-style decode. - Control scramble intensity by increasing
scrambleFramesfrom 8 to 20 — longer scrambles feel more "encrypted", shorter ones feel like a fast autofill. - Trigger the decode on
IntersectionObserverentry rather than page load for a scroll-activated reveal on long landing pages.
Watch out for
- Setting too many concurrent
setIntervalcalls (one per character for long strings) can cause jank — batch character slots into groups of 4–6 and share a single interval. - Random character width variance causes layout shift if the container is not fixed-width — use a monospace font and fixed
ch-based width on each character span. - Screen readers will read out the scrambled characters as they change — add
aria-hidden="true"on the scramble container and provide anaria-labelwith the final decoded text.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.2+ | 113+ | 111+ |
color-mix() requires Chrome 111+, Safari 16.2+, Firefox 113+. Fallback: use static hex colour for older browsers.