25 CSS Text Animations 01 / 25

CSS Typewriter Text Animation

A classic monospaced typewriter effect driven entirely by CSS steps() timing and a blinking cursor pseudo-element — no JavaScript required.

Pure CSS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="ta-01">
  <div class="ta-01__stage">
    <p class="ta-01__text ta-01__text--line1">Hello, World.</p>
    <p class="ta-01__text ta-01__text--line2">I'm a typewriter.</p>
    <p class="ta-01__text ta-01__text--line3">Pure CSS. No JS.</p>
  </div>
</div>
.ta-01, .ta-01 *, .ta-01 *::before, .ta-01 *::after {
  margin: 0; padding: 0; box-sizing: border-box;
}
.ta-01 ::selection { background: #a78bfa; color: #fff; }

.ta-01 {
  --bg: #0d0d1a;
  --green: #00ff9f;
  --cursor: #00ff9f;
  --muted: #4ade80;
  min-height: 100vh;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Courier New', Courier, monospace;
  padding: 2rem;
}

.ta-01__stage {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  align-items: flex-start;
}

.ta-01__text {
  font-size: clamp(1.1rem, 2.5vw, 1.6rem);
  color: var(--green);
  white-space: nowrap;
  overflow: hidden;
  width: 0;
  border-right: 3px solid transparent;
}

/* Line 1 */
.ta-01__text--line1 {
  animation:
    ta-01-type1 1.6s steps(13) 0.3s forwards,
    ta-01-blink 0.75s steps(1) infinite;
}
/* Line 2 */
.ta-01__text--line2 {
  animation:
    ta-01-type2 1.4s steps(19) 2.2s forwards,
    ta-01-blink2 0.75s steps(1) 2.2s infinite;
  opacity: 0;
  animation-fill-mode: forwards;
}
/* Line 3 */
.ta-01__text--line3 {
  animation:
    ta-01-type3 1.2s steps(18) 3.8s forwards,
    ta-01-blink3 0.75s steps(1) 3.8s infinite;
  opacity: 0;
  animation-fill-mode: forwards;
}

@keyframes ta-01-type1 {
  0%   { width: 0; border-right-color: var(--cursor); opacity: 1; }
  99%  { border-right-color: var(--cursor); }
  100% { width: 13ch; border-right-color: transparent; }
}
@keyframes ta-01-type2 {
  0%   { width: 0; border-right-color: var(--cursor); opacity: 1; }
  99%  { border-right-color: var(--cursor); }
  100% { width: 19ch; border-right-color: transparent; }
}
@keyframes ta-01-type3 {
  0%   { width: 0; border-right-color: var(--cursor); opacity: 1; }
  100% { width: 18ch; border-right-color: var(--cursor); }
}
@keyframes ta-01-blink  { 50% { border-right-color: transparent; } }
@keyframes ta-01-blink2 { 50% { border-right-color: transparent; } }
@keyframes ta-01-blink3 { 50% { border-right-color: transparent; } }

@media (prefers-reduced-motion: reduce) {
  .ta-01__text {
    width: auto !important;
    opacity: 1 !important;
    animation: none !important;
    border-right-color: transparent !important;
  }
}

How this works

The typewriter illusion works by animating width from 0 to 100% with steps() timing — each step snaps to the next character position instead of easing smoothly, making text appear one character at a time. The container uses overflow: hidden and white-space: nowrap so clipped characters are invisible until the width expands to reveal them.

The blinking cursor is a ::after pseudo-element styled as a vertical bar using border-right. A separate ta-01-blink keyframe toggles opacity between 1 and 0 using steps(1) for a hard on/off flash rather than a soft fade, matching the feel of a real terminal cursor.

Customize

  • Change the text by editing the HTML and matching the steps() count to the new character count so each step equals exactly one character.
  • Adjust typing speed by changing animation-duration on .ta-01__text3s is leisurely, 1.2s feels snappy.
  • Switch the cursor style from a bar to a block by replacing border-right: 3px solid with background: var(--cursor) and setting an explicit width: 0.6ch.
  • Add a second line by duplicating the element with a longer animation-delay equal to the first line's duration, creating a sequential typing effect.
  • Swap the monospace font from Courier New to JetBrains Mono or Fira Code via Google Fonts for a modern dev-terminal aesthetic.

Watch out for

  • The steps() count must exactly match the number of characters in the text — one step too few clips the last character; too many leaves a gap before the cursor.
  • This technique only works reliably with monospaced fonts where every character has the same width; proportional fonts cause uneven reveals between characters.
  • Setting width as the animated property means the element must have a fixed or known intrinsic width — percentage widths on flex children can produce unexpected results in some browsers.

Browser support

ChromeSafariFirefoxEdge
4+ 4+ 3.5+ 4+

steps() timing is universally supported; no vendor prefixes needed in any modern browser.

Search CodeFronts

Loading…