Typewriter Delete
A recursive setTimeout types and deletes words in a loop — CSS blinking cursor, independent typing and deletion speeds, infinite cycle.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Text has 56 effects, including 4 free. Explore the category →
3 usage examples



How it works
The animation relies on a recursive setTimeout — no requestAnimationFrame. On each tick, a single textContent property on the <span> is updated with a substring(0, n) of the current word: n increments on typing, decrements on deletion. Two delays coexist: 100 ms per character added, 50 ms per character removed (2× faster).
The blinking cursor is entirely CSS: a border-right: 3px solid #22d3ee set on the <span> itself, animated by @keyframes blinkCursor which toggles border-color: transparent at 50% with a step-end timing function — the blink is an instant switch, not a gradual fade.
When charIndex reaches the word length, a 1 500 ms pause elapses before deletion starts (isDeleting = true). When the index drops back to 0, a 300 ms pause precedes the next word — obtained via typewriterIndex = (typewriterIndex + 1) % typewriterWords.length. The array loops infinitely.
The replayTypewriterDelete() function clears textContent, resets all counters, and restarts the cycle from the first word — intended to be wired to a button or a business event. It must be exposed on window when called from an inline onclick attribute (outside the IIFE).
Accessibility
- No prefers-reduced-motion support: neither the CSS nor the JS checks this preference — the cursor blinks and words cycle indefinitely even if the OS signals reduced motion. Add this check manually if your audience is sensitive to motion.
- Color contrast:
#22d3eetext on#0a0a0fbackground achieves a ratio > 8:1 — well above the WCAG AA threshold (4.5:1). - Text is real DOM
textContent, not an image: screen readers read the displayed words, but the absence ofaria-liveorrole="status"means content changes are not announced automatically. - The Replay button is a native
<button>— keyboard-focusable (Tab + Enter), in the normal tab order. - The CSS cursor (
border-rightblinking) is purely decorative and requires no text or aria equivalent.
Browser compatibility
Uses setTimeout, textContent, and CSS animations — available in all browsers for over 10 years. Zero external dependencies.
If JavaScript is disabled, the <code><span></code> stays empty — no fatal error, the page stays functional. The CSS cursor blinks on its own.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="demo-preview">
<span class="demo-text text-typewriter-delete" id="typewriterDeleteText"></span>
<!-- Replay button calls replayTypewriterDelete() — expose it on window if needed -->
<button class="replay-btn" onclick="replayTypewriterDelete()">Replay</button>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| typewriterWords (JS, line 3) | ['CREATE', 'DESIGN', 'BUILD', 'INSPIRE'] | Array of words to cycle through — replace with your own strings. Unlimited length, scrolling is modular. |
| .text-typewriter-delete { color } | #22d3ee | Text AND cursor color in one change — border-color inherits from color if not overridden separately. |
| .text-typewriter-delete { border-right-width } | 3px | Cursor thickness. 2px for a thin cursor, 4–5px for a thick terminal-style cursor. |
| delay = 100 (typing, JS) | 100 ms | Delay between each character added. 60–80 ms = fast typing, 150–200 ms = human-like pace with hesitations. |
| delay = 50 (deletion, JS) | 50 ms | Delay between each character removed. Deletion is 2× faster than typing by default — adjust the ratio to taste. |
| delay = 1500 (post-word pause, JS) | 1 500 ms | How long the complete word is shown before deletion begins. 800 ms = lively pace, 2 500 ms = comfortable reading. |
| delay = 300 (between-word pause, JS) | 300 ms | Wait between end of deletion and start of next word. 0–100 ms gives a mechanical feel, 500 ms a more relaxed rhythm. |
| .demo-text { font-size } | 2.5rem | Text size. The effect works from 0.9rem (input placeholder) to 5rem (full-screen hero) with no JS changes. |
FAQ
prefers-reduced-motion. To respect this preference, add at the top of the start function: if (window.matchMedia('(prefers-reduced-motion:reduce)').matches) return; — the effect will not start on affected systems.window.replayTypewriterDelete = replayTypewriterDelete; inside the IIFE (before closing), then call window.replayTypewriterDelete() from any event — section reload, end of video, scroll trigger, etc.id="typewriterDeleteText". For multiple instances, refactor the logic into a factory function: function makeTypewriter(el, words, opts) { … } with its own local state (index, charIndex, isDeleting, timer), and call it on each target element.