14 CSS Typewriter Effect Designs 01 / 14

CSS Typewriter steps() Cursor

The canonical CSS-only typewriter: width animates from 0 to a fixed ch value using steps() timing, paired with a blinking border-right cursor — no JS at all.

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

The code

<div class="tw-01">
  <div class="tw-01__stage">
    <p class="tw-01__label">Frontend Developer</p>
    <h1 class="tw-01__text">Hello, World.</h1>
    <p class="tw-01__sub">Building beautiful interfaces with code.</p>
  </div>
</div>
.tw-01, .tw-01 *, .tw-01 *::before, .tw-01 *::after { box-sizing: border-box; margin: 0; padding: 0; }
.tw-01 ::selection { background: #a78bfa; color: #0f0a1e; }

.tw-01 {
  --bg: #0f0a1e;
  --surface: #1a1033;
  --accent: #a78bfa;
  --text: #e2d9f3;
  --muted: #6b5fa0;
  font-family: 'Courier New', Courier, monospace;
  min-height: 340px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
}

.tw-01__stage {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.tw-01__label {
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0;
  animation: tw-01-fadein 0.4s 0.2s forwards;
}

.tw-01__text {
  font-size: clamp(1.8rem, 5vw, 3rem);
  font-weight: 700;
  color: var(--text);
  overflow: hidden;
  white-space: nowrap;
  width: 0;
  border-right: 3px solid var(--accent);
  animation:
    tw-01-type 1.1s steps(13) 0.8s forwards,
    tw-01-blink 0.75s steps(2) 0.8s infinite;
}

.tw-01__sub {
  font-size: 0.95rem;
  color: var(--muted);
  overflow: hidden;
  white-space: nowrap;
  width: 0;
  animation: tw-01-sub 1.5s steps(38) 2.2s forwards;
}

@keyframes tw-01-type {
  from { width: 0; }
  to   { width: 13ch; }
}
@keyframes tw-01-sub {
  from { width: 0; }
  to   { width: 38ch; }
}
@keyframes tw-01-blink {
  0%, 100% { border-color: var(--accent); }
  50%      { border-color: transparent; }
}
@keyframes tw-01-fadein {
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .tw-01__text { width: 13ch; animation: none; border-right-color: var(--accent); }
  .tw-01__sub  { width: 38ch; animation: none; }
  .tw-01__label { opacity: 1; animation: none; }
}

How this works

The element has overflow: hidden; white-space: nowrap and animates from width: 0 to width: 22ch using steps(22) — each step snaps the width to reveal exactly one more character. Because steps() produces discrete jumps rather than a smooth ease, the text appears to be typed in real time without any scripting.

A second border-right: 3px solid animation toggles between the accent colour and transparent at steps(2), creating the hard on/off blink of a real terminal cursor. Both animations run on the same element; the cursor animation loops infinitely while the typing animation plays once with animation-fill-mode: forwards to hold the final state.

Customize

  • Match steps(N) and width: Nch to the exact character count of your string — one step per character for pixel-perfect snapping.
  • Change cursor style from a right-border to an underline by switching to border-bottom and removing border-right.
  • Add animation-delay: 0.5s and animation-fill-mode: both to pause before the text starts appearing, useful for hero section entrances.
  • Use font-variant-numeric: tabular-nums on numeric strings to keep character widths uniform across all digits.

Watch out for

  • Proportional fonts break the ch-based character count — this technique only works reliably with monospace fonts where every glyph is exactly 1ch wide.
  • If you resize the container the cursor may drift; pin the element to width: fit-content; max-width: Nch and avoid percentage widths on the parent.
  • The blinking cursor is invisible in some high-contrast Windows accessibility modes — add a forced-colors: active fallback that makes the border ButtonText.

Browser support

ChromeSafariFirefoxEdge
4+ 4+ 3.5+ 4+

Universally supported — the oldest reliable CSS animation technique.

Search CodeFronts

Loading…