I'm a
Crafting fast, accessible web experiences since 2018.
Pure CSS animation that reveals text character by character behind a blinking indigo cursor — zero JavaScript, a single <span>.
This effect is free — the Text category contains 56 effects total, including 4 free. Effect.Labs has 811 vanilla effects. Explore the category →
I'm a
Crafting fast, accessible web experiences since 2018.
The effect relies entirely on two CSS @keyframes with no JavaScript whatsoever. The width property starts at 0 and animates to 100% using the steps(20) timing function: instead of a smooth interpolation, the browser jumps through 20 discrete increments, simulating letter-by-letter appearance. overflow: hidden hides excess text to the right until each step is reached; white-space: nowrap prevents line breaks that would break the illusion.
The typing animation follows a plateau curve: from 0% to 30% of the cycle, the width grows from 0 to 100% (typing); it holds full from 30% to 60% (readable pause); then from 60% to 90% it returns to 0 (erasure, same steps(20) reversed). From 90% to 100%, width stays at 0 — a micro-pause before the next typing cycle.
The cursor is simulated by border-right: 3px solid rgb(99, 102, 241) set directly on the <span> — no pseudo-element, no extra DOM node. The blink animation (0.5 s, step-end timing, alternate direction) switches the border color between indigo and transparent: the instant jump of step-end reproduces the square blink of a real terminal cursor, with no fade.
max-width: fit-content prevents the span from stretching across the full line when inside a wide flex or grid container — without it, the cursor border would appear at the right of the block, far from the last character. Both animations loop infinitely and independently on the same element.
prefers-reduced-motion media query. The animation runs continuously even for motion-sensitive users. Recommended fix: add @media (prefers-reduced-motion: reduce) { .text-typing { animation: none; width: 100%; border-right: none; } }.<span> holds real text read normally by screen readers — no information is encoded in the animation itself.aria-live attribute on the element: if the text changes dynamically via JS (phrase cycling), add aria-live="polite" so updates are announced.rgb(99, 102, 241) — indigo-500) achieves a ratio ≥ 4.5:1 on dark backgrounds (#0a0a0f); text color inherits from the integration context, whose contrast is the integrator's responsibility.Pure CSS effect — @keyframes, steps(), and max-width: fit-content cover the modern browser landscape. Zero dependencies.
On Firefox before 94, <code>max-width: fit-content</code> is not recognized: the cursor border may stretch visually, without blocking text readability. Without <code>@keyframes</code> support, text displays fully and statically — no JS errors, content always readable.
Copy the three blocks into your page. No dependencies.
<span class="demo-text text-typing">Hello World</span>
.demo-text {
font-size: 2.5rem;
font-weight: 800;
letter-spacing: -0.02em;
}
.text-typing {
overflow: hidden;
border-right: 3px solid rgb(99, 102, 241);
white-space: nowrap;
animation: 3s steps(20) 0s infinite normal none running typing, 0.5s step-end 0s infinite alternate none running blink;
width: 0px;
max-width: fit-content;
}
@keyframes typing {
0%, 90%, 100% {
width: 0px;
}
30%, 60% {
width: 100%;
}
}
@keyframes blink {
50% {
border-color: transparent;
}
}
// Effet CSS pur — pas de JavaScript nécessaire
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| steps(20) | 20 | Number of discrete typing animation steps — should approximate the character count of the text (spaces included). Too low: chunky jumps; too high: nearly smooth, loses the typewriter feel. |
| 3s (typing duration) | 3s | Total cycle duration (typing + hold + erasure). Increase to 4–6 s for long text or a comfortable reading pace. |
| 0.5s (blink duration) | 0.5s | Cursor blink speed. 0.5 s = classic terminal; 1 s = slow cursor; 0.3 s = nervous, fast cursor. |
| border-right: 3px solid rgb(99, 102, 241) | 3px, indigo-500 | Cursor thickness and color. Swap the color for your brand hue. 4 px gives an IDE-style block cursor; 2 px gives a thin editorial cursor. |
| font-size: 2.5rem (.demo-text) | 2.5rem | Size of the animated text. Large hero: 3–4 rem; subtitle: 1–1.5 rem; terminal: 0.9–1.1 rem. |
| font-weight: 800 (.demo-text) | 800 | Text weight. 400 for body or terminal text; 700 for a standard heading; 900 for maximum impact. |
| letter-spacing: -0.02em (.demo-text) | -0.02em | Character spacing. Negative = tight (modern, bold style); 0 or slightly positive = open (editorial, body text). |
Count the characters in your text, including spaces, and use a close value. For 'Hello World' (11 chars), steps(11) to steps(14) is natural. Too few steps produce visible jumps between character groups; too many make the progression nearly continuous, losing the typewriter feel.
The CSS effect alone loops a fixed text. To rotate between phrases, two approaches: (1) CSS only — stack <span class="text-typing"> elements with position: absolute and animation-delay values offset by multiples of the cycle duration; (2) light JS — listen for the animationiteration event on the span and update its textContent between cycles.
Yes. The effect sets no background or text color. Only adjust border-right-color to keep the cursor visible against your background (e.g. #1e293b on white). Text color inherits from your stylesheet; no background property is defined by the effect itself.