Skip to content

CSS typewriter effect without JavaScript

A typing animation in pure CSS, with a blinking caret, using steps() and the ch unit. The full code, how it works line by line, its limits with long or multi-line text, and how to make it respect reduced motion and screen readers.

Short answer: give the text a monospace font, white-space: nowrap and overflow: hidden, then animate its width from 0 to the number of characters in ch with steps(n), where n is the number of characters. A border-right that blinks with a second animation is the caret. No JavaScript needed.

What is the code?

<p class="typewriter">Hello, motion.</p>
.typewriter {
  width: 14ch;                       /* 14 characters in "Hello, motion." */
  font: 600 26px/1.3 ui-monospace, Menlo, monospace;
  white-space: nowrap;
  overflow: hidden;
  border-right: .08em solid #d864ff; /* the caret */
  animation: typewriter 4s steps(14) infinite,
             typewriter-caret .75s step-end infinite;
}
@keyframes typewriter {
  0% { width: 0 }
  45%, 75% { width: 14ch }
  95%, 100% { width: 0 }
}
@keyframes typewriter-caret {
  50% { border-color: transparent }
}
@media (prefers-reduced-motion: reduce) {
  .typewriter { animation: none; }
}

How does it work?

  • ch is the width of one character in the current font. In a monospace font every character has that width, so 14ch is exactly the width of 14 characters.
  • steps(14) makes the width jump in 14 equal steps instead of sliding, so one whole character appears at a time, like typing.
  • overflow: hidden hides the characters the width has not reached yet, and nowrap keeps them on one line.
  • The keyframes type the text in the first 45% of the cycle, hold it until 75%, then delete it by 95%, and loop. For a single run that stays typed, use 0% { width: 0 } 100% { width: 14ch } with forwards and no infinite.
  • The caret is the right border. step-end switches it on and off sharply, without a fade, the way a real caret blinks.

Why count the characters?

The number in width, in steps() and in the keyframes must be the length of your text, spaces and punctuation included. "Hello, motion." is 14. If the number is too small the text is cut; too large and the caret waits after the last letter. With a custom property you set it once:

.typewriter {
  --n: 14;
  width: calc(var(--n) * 1ch);
  animation: typewriter 4s steps(var(--n)) infinite,
             typewriter-caret .75s step-end infinite;
}
@keyframes typewriter {
  0% { width: 0 }
  45%, 75% { width: calc(var(--n) * 1ch) }
  95%, 100% { width: 0 }
}

What are its limits?

  • One line only. The trick reveals by width, so it cannot wrap. For a paragraph, use JavaScript or reveal word by word with staggered animations.
  • Monospace only. In a proportional font an "i" is narrower than an "m", so steps no longer land on letter edges. Fonts with tabular digits are fine for numbers.
  • The text must fit. On a narrow phone, a long line overflows. Keep it short, or lower the font size on small screens.

How do you keep it accessible?

Screen readers read the text in the HTML, all of it, whatever the animation shows, so nobody hears it letter by letter. The reduced-motion rule shows the whole text, still, to people who ask their system for less motion. Do not use a typewriter for text people need to read quickly, like an error message: they have to wait for it.

Where can you check this?

Can you change it without counting?

Motion Studio has this Typewriter in its Text kind, among 150 motions. Type your own text and it counts the characters for you, then set the font, the weight, the caret colour and the speed, scrub the time bar to check each step, and copy the HTML and CSS, or a React, Vue or Svelte component. The reduced-motion rule is included.

The tool

Do it in your browser.

Design

Motion Studio

150 ready-made motion graphics, from loaders and text effects to animated backgrounds, hover effects, charts and cards that follow the pointer, all playing at once: change colours, size, speed and easing, then take HTML and CSS, React, Vue or Svelte, or a GIF, a video or an animated SVG.

Keep reading

More from the blog.

Every post

Contact