Snippet
Streaming Text Reveal
ChatGPT-style token-by-token text reveal with a blinking cursor and stop button.
Last updated: October 2026
What this snippet does: Renders text one character at a time, mimicking the streaming output of AI models like ChatGPT and Claude. Includes a blinking cursor and a stop button. Pure HTML, CSS, and vanilla JavaScript — no dependencies, no build step.
🎬 Live Demo
Click Start to see the streaming effect.
💡 The cursor blinks while streaming and disappears when done.
How It Works
Text is appended one character at a time using setInterval. The cursor is a separate span that stays at the end. When streaming finishes, the cursor is removed.
HTML
<div class="stream-container">
<div class="stream-output" id="output"></div>
<div class="stream-controls">
<button id="start">▶ Start</button>
<button id="stop" disabled>⏹ Stop</button>
<button id="reset">↺ Reset</button>
</div>
</div>
CSS
.stream-output {
font-family: 'Inter', sans-serif;
font-size: 1rem;
line-height: 1.7;
color: #e2e8f0;
white-space: pre-wrap;
word-wrap: break-word;
min-height: 3rem;
}
.stream-cursor {
display: inline-block;
width: 0.5ch;
height: 1.1em;
background: #3b82f6;
margin-left: 2px;
vertical-align: text-bottom;
animation: blink 1s step-end infinite;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
JavaScript
const TEXT = "Your streaming text here...";
const output = document.getElementById('output');
const startBtn = document.getElementById('start');
const stopBtn = document.getElementById('stop');
const resetBtn = document.getElementById('reset');
let intervalId = null;
let cursor = null;
function createCursor() {
cursor = document.createElement('span');
cursor.className = 'stream-cursor';
output.appendChild(cursor);
}
function removeCursor() {
if (cursor && cursor.parentNode) {
cursor.parentNode.removeChild(cursor);
}
cursor = null;
}
function startStream() {
if (intervalId) return;
output.textContent = '';
createCursor();
let i = 0;
intervalId = setInterval(() => {
if (i >= TEXT.length) {
stopStream();
return;
}
cursor.insertAdjacentText('beforebegin', TEXT[i]);
i++;
}, 25);
startBtn.disabled = true;
stopBtn.disabled = false;
}
function stopStream() {
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
removeCursor();
startBtn.disabled = false;
stopBtn.disabled = true;
}
function resetStream() {
stopStream();
output.textContent = '';
}
startBtn.addEventListener('click', startStream);
stopBtn.addEventListener('click', stopStream);
resetBtn.addEventListener('click', resetStream);
Using a Real AI API
Replace the simulated interval with a real streaming fetch:
async function streamFromAPI(prompt) {
output.textContent = '';
createCursor();
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
cursor.insertAdjacentText('beforebegin', chunk);
}
removeCursor();
}
Pro tip: For OpenAI/Anthropic APIs, set stream: true in the request body and read chunks from the response body.
🎨 Customization
| Property | Effect | Default |
|---|---|---|
interval | Speed of reveal | 25ms |
background (cursor) | Cursor color | #3b82f6 |
font-size | Text size | 1rem |
🎯 Use Cases
- AI chat interfaces — ChatGPT, Claude, Perplexity clones
- Code assistants — streaming completions into an editor
- Documentation generators — progressive reveal of long text
- Landing page hero — attention-grabbing animated headline
- Terminal emulators — simulate command output
⚠️ Common Mistakes
- Appending with innerHTML +=. Re-parses the DOM each time. Use
insertAdjacentText. - Using setInterval for real APIs. Use
ReadableStreamreader for actual streaming. - Forgetting to clear the interval. Memory leak if the user navigates away.
- Not handling unicode. Split emoji incorrectly if you slice strings naively.
- Auto-scrolling jitter. Use
scrollIntoView({ block: 'nearest' }).
❓ Frequently Asked Questions
How does streaming text work in AI apps?
The model returns tokens over a stream (SSE or WebSocket). The UI appends each token to the DOM as it arrives, giving the effect of text being typed in real time.
Do I need a library for streaming text?
No. This snippet uses vanilla JavaScript with setInterval or requestAnimationFrame. Works in any modern browser without dependencies.
How do I stop streaming mid-way?
Store the interval or animation frame ID and clear it on stop. This snippet includes a Stop button that does exactly that.
Can I use this with a real AI API?
Yes. Replace the simulated interval with your actual API call using fetch() with a ReadableStream reader or EventSource for SSE.
Does this work on mobile?
Yes. The layout is responsive and uses no fixed widths. The cursor animation uses CSS only.