Home / Snippets / Animation /

Typewriter text

A pure CSS typewriter effect using steps() timing to reveal text character by character, with a blinking cursor via border-right.

Hello, I'm a typewriter effect.

Pure CSS. No JavaScript.

Widely Supported
animationtypographyno-js

Quick implementation

/* HTML: <p class="typewriter">Your text here</p> */

.typewriter {
  font-family: monospace;
  white-space: nowrap;
  overflow: hidden;
  border-right: 3px solid oklch(0.72 0.19 265);
  width: 0;
  animation:
    typing 3s steps(26, end) forwards,
    blink 0.75s step-end infinite;
}

@keyframes typing {
  from { width: 0; }
  to   { width: 100%; }
}

@keyframes blink {
  0%, 100% { border-color: oklch(0.72 0.19 265); }
  50%       { border-color: transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .typewriter {
    animation: none;
    width: 100%;
    border-right-color: transparent;
  }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer implementing a typewriter text animation
using only CSS — no JavaScript.

Goal: Animate a line of text so it appears to be typed character by character,
followed by a blinking cursor. The effect should be fully self-contained in CSS.

Technical constraints:
- Use @keyframes typing that animates width from 0 to 100% (or 0ch to Nch).
- Use steps(N, end) as the timing function, where N equals the number of
  characters in the string — this creates discrete jumps, one per character.
- Apply overflow: hidden and white-space: nowrap on the element so characters
  are clipped until revealed.
- Simulate a cursor using border-right: 3px solid on the element itself.
- Use @keyframes blink that toggles border-color between the accent color and
  transparent using step-end timing, at ~0.75s per cycle.
- Use oklch() for all colors — no hex or rgba values.
- Include @media (prefers-reduced-motion: reduce) that sets animation: none,
  width: 100%, and border-right-color: transparent so the full text is
  immediately visible without motion.

Framework variant (pick one):
A) Vanilla CSS — a single .typewriter class with both animations combined in
   the animation shorthand, ready to drop on any block-level text element.
B) React component — accept a text string and optional duration prop; compute
   the character count automatically and apply it as a CSS custom property
   (--char-count) used inside steps(var(--char-count), end) in the stylesheet.

Edge cases to handle:
- The steps() count must exactly match the character count or the reveal will
  appear too fast or stutter — document this requirement clearly.
- Variable-width fonts cause uneven character reveals; use a monospace font
  (e.g. monospace, JetBrains Mono) or ch units for a pixel-perfect effect.
- For multi-line sequences, chain animations with animation-delay so the
  second line starts only after the first finishes typing.
- Ensure the cursor blink stops after typing is complete if desired, by setting
  the blink animation iteration count to match a finite duration.

Return CSS only (or a React component if variant B is chosen).

Why this matters in 2026

Typewriter animations remain a staple of developer portfolios and product landing pages because they draw the eye and communicate a programming aesthetic. What has changed is that CSS can now handle the effect entirely — no JavaScript setTimeout loop, no third-party library. The steps() timing function, long available but underused, is the key: it turns a smooth animation into a sequence of discrete jumps that align exactly with character boundaries in a monospace font.

The technique also demonstrates a broader CSS principle: many effects that once required JavaScript can be expressed declaratively. A typewriter effect built in CSS survives server-side rendering, works before the JavaScript bundle parses, and adds zero runtime overhead — the browser's animation engine handles it entirely off the main thread.

The logic

Three CSS properties combine to create the illusion. First, overflow: hidden and white-space: nowrap keep all the text on one line and clip anything that falls outside the element's box. Second, the typing keyframe animates width from 0 to 100% — ordinarily this would reveal text as a smooth horizontal wipe. The critical step is the timing function: steps(N, end) divides the animation into exactly N equal-duration discrete jumps with no interpolation between them. When N equals the character count, each jump reveals exactly one character.

The cursor is a trick of geometry: border-right: 3px solid on the element itself becomes a blinking caret. The blink keyframe toggles border-color between the accent color and transparent using step-end timing — which means the transition happens instantaneously at each 50% mark, producing the characteristic hard on/off blink rather than a soft fade.

Using ch units instead of a percentage is an alternative approach: set width: 0ch to width: 26ch where 26 is the character count. In a monospace font, one ch is exactly the width of one character (the "0" glyph), making the alignment mathematically exact. The percentage approach works well too when the element's container is sized to fit the text.

Accessibility & performance

The prefers-reduced-motion: reduce query is especially important here. The typewriter animation involves rapid, repetitive movement — both the expanding text reveal and the continuously blinking cursor — which can be distracting or disorienting for users with vestibular disorders or attention sensitivities. Setting animation: none, width: 100%, and border-right-color: transparent inside the query ensures those users see the complete text immediately with no motion.

One performance note: width animation triggers layout recalculation on every frame, unlike transform or opacity. For a single short text element this is negligible, but avoid applying the typewriter effect to many elements simultaneously on a complex page. If performance is critical, the clip-path or max-width approach can sometimes avoid layout thrashing, though in practice a single animated element has no measurable impact on modern hardware.

Screen readers read the full text content regardless of the visual animation state — the DOM text is always present, only its visual clipping changes. This means no additional ARIA attributes are needed for the basic pattern, though aria-live regions should be avoided here since the text is not dynamically injected.