Skip to content
Created: March 7, 2026

These animations affect visual presentation rather than text content. Provide reduced-motion styles in a production page and avoid disruptive cursor blinking.

Multi-line text can be achieved through steps() + width + overflow + white-space.

1. Basic version: single line typing effect ​

html
<div class="typewriter">
  <p>你好,这是一个打字效果的演示。</p>
</div>
css
.typewriter {
  width: fit-content; /* 让容器适应内容 */
  font-family: 'Courier New', monospace; /* 等宽字体,便于精确控制 */
}

.typewriter p {
  width: 0;
  overflow: hidden;
  white-space: nowrap;
  border-right: 2px solid; /* 模拟光标 */
  animation: 
    typing 4s steps(20, end) forwards, /* 20需替换为实际字符数 */
    blink-caret 0.75s step-end infinite;
}

@keyframes typing {
  from { width: 0 }
  to { width: 40ch } /* 使用ch单位(字符宽度)更精确 */
}

@keyframes blink-caret {
  from, to { border-color: transparent }
  50% { border-color: black; }
}

Principle ​

The core is:

steps(n)

Split the animation into n steps, like characters appearing one by one.

width: 40ch

ch normally uses the advance width of the zero glyph, not every character’s actual width; monospace fonts suit this example better.

2. Width reveal for multiline text ​

The following width animation reveals text while allowing reflow; it is not true line-by-line or character-by-character output. See the next section for a line-by-line example.

HTML ​

html
<div class="typing-multi">
Hello World
This is CSS typing effect
Support multiple lines
Pure CSS animation
</div>

CSS ​

css
.typing-multi {
  width: 30ch;
  overflow: hidden;
  border-right: 2px solid #000;
  white-space: pre-wrap;
  line-height: 1.6;
  
  animation:
    typing-multi 6s steps(120) forwards,
    blink .8s infinite;
}

@keyframes typing-multi {
  from {
    width: 0;
  }
  to {
    width: 30ch;
  }
}

@keyframes blink {
  50% {
    border-color: transparent;
  }
}

3. Real multi-line output line by line ​

If you want output line by line.

HTML ​

html
<div class="container">
  <p class="line">Hello World</p>
  <p class="line">This is CSS typing effect</p>
  <p class="line">Support multiple lines</p>
  <p class="line">Pure CSS animation</p>
</div>

CSS ​

css
.line {
  width: 0;
  overflow: hidden;
  white-space: nowrap;
  border-right: 2px solid transparent;
}

.line:nth-child(1) {
  animation: typing 2s steps(20) forwards, cursor 0.5s step-end 4 alternate;
}

.line:nth-child(2) {
  animation: typing 2s steps(25) 2s forwards, cursor 0.5s step-end 4 alternate 2s;
}

.line:nth-child(3) {
  animation: typing 2s steps(22) 4s forwards, cursor 0.5s step-end 4 alternate 4s;
}

.line:nth-child(4) {
  animation: typing 2s steps(22) 6s forwards, cursor 0.5s step-end infinite alternate 6s;
}

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

@keyframes cursor {
  from { border-right-color: black; }
  to   { border-right-color: transparent; }
}

Effect:

Hello World
↓
This is CSS typing effect
↓
Support multiple lines
↓
Pure CSS animation

Type line by line.

4. Enterprise-level optimized version (recommended) ​

Suitable for React/Vue/AI Chat UI.

css
.typing {
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  border-right: 2px solid;
  width: 0;
  animation: typing var(--time) steps(var(--steps)) forwards;
}

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

HTML

html
<div class="typing" style="--time:3s;--steps:30">
  Hello this is typing effect
</div>

Advantages:

  • Dynamically control the number of characters
  • Componentization
  • Suitable for AI Chat

5. A more realistic solution for AI Chat scenarios ​

The same effect as ChatGPT / Claude:

CSS + JS

html
<div id="app">
  <div id="text"></div>
</div>
css
#text {
  font-size: 18px;
  line-height: 1.6;
  /* border-right: 2px solid black; */
  display: inline-block;
  padding-right: 4px;
  animation: blink 1s infinite;
   white-space: pre-wrap;
}
#text::after {
  content: "|";
  margin-left: 2px;
  animation: blink 1s infinite;
}

@keyframes blink {
  50% {
    border-color: transparent;
  }
}

JS control character flow:

js
function typing(el, text, speed = 40) {
  let i = 0
  let last = 0

  function frame(time) {
    if (time - last > speed) {
      el.textContent += text[i]
      i++
      last = time
    }

    if (i < text.length) {
      requestAnimationFrame(frame)
    }
  }

  requestAnimationFrame(frame)
}

const el = document.getElementById("text")

typing(
  el,
  "Hello World!\nThis is a typing effect demo.\nSupport multiple lines.",
  60
)

6. Limitations of pure CSS ​

Pure CSS has several limitations:

LimitationsReasons
steps cannot be automatically calculated based on character countCSS does not know text length
Difficulty controlling multiple linesManual delay required
Dynamic text does not workCSS static

So AI Chat must be controlled by JS.

7. How to achieve typing effect? ​

If it is static text, you can use CSS steps() to achieve:

  • overflow:hidden
  • width
  • steps(n)

If it is dynamic AI streaming text, JS needs to control the character stream, or directly use SSE/WebSocket's chunk update.