Source Code

<div class="acs-wrap">
  <div class="acs-thread" id="acsThread">
    <div class="acs-row acs-row-user">
      <div class="acs-bubble acs-bubble-user">What's a good way to explain event delegation in JavaScript?</div>
    </div>
    <div class="acs-row acs-row-ai" id="acsAiRow">
      <div class="acs-avatar">AI</div>
      <div class="acs-ai-col">
        <div class="acs-bubble acs-bubble-ai" id="acsAiBubble"><span id="acsAiText"></span><span class="acs-cursor" id="acsCursor"></span></div>
        <button class="acs-stop-btn" id="acsStopBtn">
          <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
          Stop generating
        </button>
        <div class="acs-actions" id="acsActions" hidden>
          <button class="acs-icon-btn" id="acsCopyBtn" title="Copy">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
          </button>
          <button class="acs-icon-btn" id="acsRegenBtn" title="Regenerate">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
          </button>
        </div>
      </div>
    </div>
  </div>
</div>

AI Chat Streaming Bubble — Free HTML CSS JS Snippet

AI Chat Streaming Bubble · Misc · Plain HTML, CSS & JS · Live preview

What's included

Features

Word-by-word streaming simulation driven by setInterval over a hardcoded response string
Blinking text cursor shown only while streaming is active, via a CSS keyframe animation
"Stop generating" button visible only during streaming, immediately halting the interval on click
Post-completion copy button using the Clipboard API with brief visual confirmation feedback
Regenerate button that restarts the same simulated stream from the beginning
Distinct user and assistant bubble styling with an avatar for the assistant
No external API calls — entirely self-contained and safe to drop into any static page
Clean state handoff between streaming and completed states with no leftover UI artifacts

About this UI Snippet

AI Chat Streaming Bubble — Simulated Word-by-Word LLM Response Bubble

Screenshot of the AI Chat Streaming Bubble snippet rendered live

Chat interfaces built on top of language models typically show the assistant's reply appearing incrementally rather than all at once, which this snippet reproduces entirely on the front end using a hardcoded response string and setInterval — no real API call is involved.

Word-by-word streaming via setInterval

The response is split into an array of words up front. startStreaming() runs an interval every 55ms that appends the next word (with a leading space, except for the first) to the bubble's text content and advances an index counter, until every word has been appended, at which point finishStreaming() clears the interval.

A blinking cursor tied to streaming state

A thin <span class="acs-cursor"> sits right after the streaming text and blinks via a CSS @keyframes opacity animation. It's shown for the duration of the interval and hidden (via the hidden attribute) the moment finishStreaming() runs, so the blinking cursor visually communicates "still generating" without any extra JS state to track.

A stop button that actually halts generation

"Stop generating" is only visible while intervalId is active; clicking it calls finishStreaming(), which clears the interval immediately, leaving whatever words had already been appended in place — an accurate simulation of interrupting a real stream mid-response.

Post-completion actions

Once streaming finishes, the stop button is hidden and a small action row appears with copy and regenerate icon buttons. Copy uses navigator.clipboard.writeText and gives brief visual feedback by toggling a class; regenerate clears any leftover interval and calls startStreaming() again to replay the same response from scratch.

Step by step

How to Use

  1. 1
    Load the snippetClick "AI Chat Streaming Bubble" in the sidebar to load its HTML, CSS, and JS into the editor panels. The preview updates instantly.
  2. 2
    Edit the codeModify any panel — HTML, CSS, or JS. The preview refreshes as you type. Use Reset in each panel header to restore the original.
  3. 3
    Preview on devicesClick the Mobile (375px), Tablet (768px), or Desktop buttons in the preview header to check responsiveness.
  4. 4
    Export in your formatClick "HTML" to download a standalone file, "JSX" for a React component, "Tailwind" for a React + Tailwind CSS component, "Tailwind HTML" for a standalone HTML file with Tailwind CDN, "Vue" for a Vue 3 SFC with <template>/<script setup>/<style scoped>, or "Angular" for a standalone Angular .component.ts file. "Copy all" copies the full code to clipboard.
  5. 5
    Save your versionClick "Save as", type a name, and press Enter. Your snippet saves to IndexedDB and appears in the Saved tab.

Real-world uses

Common Use Cases

AI chat product UI prototyping
Demo a streaming assistant response without wiring up a real model or API key.
Reference implementation for a streaming bubble component
A starting point for wiring real token-streaming from a backend into the same visual pattern.
Marketing pages and product demos for AI features
Show a believable live-feeling AI interaction without any backend dependency.
Teaching setInterval-based text animation
A clear, small example of incrementally revealing text and coordinating UI state around it.

Got questions?

Frequently Asked Questions

No. The assistant reply is a hardcoded string revealed word-by-word using setInterval purely for visual effect — no network request or API key is involved anywhere in this snippet.

The click handler calls clearInterval on the active streaming interval immediately, leaving whatever text had already been appended in place, hides the stop button, and reveals the copy/regenerate actions.

Replace the setInterval word-appending loop inside startStreaming() with your fetch/EventSource stream handler, appending each real token chunk to textEl.textContent as it arrives, and call finishStreaming() when the stream ends.

This is a simulation with one fixed RESPONSE string. In a real implementation, regenerate would trigger a new API call and stream back a genuinely different response.