Chat UI — Free HTML CSS JS Messaging Snippet

Chat Bubble UI · Layouts · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

sendMsg() creates .msg.outgoing div with text and formatted timestamp
msgs.scrollTop = msgs.scrollHeight auto-scrolls after each message
Enter key onkeydown sends message without clicking the button
Incoming .msg.incoming and outgoing .msg.outgoing distinct visual styles
max-width: 70% on bubbles prevents full-width messages
Online status green dot in header with absolute positioning
overflow-y: auto on message container with scrollbar hidden
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons
Live split-pane editor — preview updates as you type

About this UI Snippet

Chat UI — Dynamic Message Append, Outgoing/Incoming Bubbles & Auto-Scroll

Screenshot of the Chat Bubble UI snippet rendered live

A chat UI is the messaging interface pattern used in customer support widgets like the floating chat widget, social apps, AI chatbots (see the AI prompt composer), and collaboration tools. This snippet provides the complete UI: a scrollable message list with outgoing and incoming bubble styles, a text input with send button, Enter key support, and auto-scroll to the latest message.

Sending messages

sendMsg() reads the input value, trims whitespace, and returns early if empty. It creates a div.msg.outgoing element, sets its innerHTML with the message text and current time via toLocaleTimeString. The message is appended to the #msgs container and the container scrolls to scrollTop = scrollHeight to show the new message. The input is cleared.

Incoming vs outgoing bubbles

.msg.outgoing aligns to the right with an accent background. .msg.incoming aligns to the left with a grey background. Both use max-width: 70% to prevent bubbles from spanning the full chat width. The timestamp sits below the bubble text in a smaller, dimmer colour.

Enter key support

The input has onkeydown="if(event.key==='Enter') sendMsg()" — pressing Enter sends the message without needing to click the button.

Auto-scroll

msgs.scrollTop = msgs.scrollHeight scrolls the message container to its maximum height after appending each message, keeping the latest message always visible.

The message append pattern

sendMessage() creates a new .message div with class "outgoing" and the message text, then appends it to .messages. A short setTimeout creates the simulated incoming reply. Each message has a .time span showing the current time formatted as HH:MM. The container uses overflow-y: auto and a JavaScript call to scrollTop = scrollHeight after each append to auto-scroll to the most recent message.

Message bubbles with CSS

Outgoing messages float right using margin-left: auto. Incoming messages have no margin override so they sit at the left. Both use border-radius with an asymmetric corner: outgoing messages have border-bottom-right-radius: 4px (the "tail" corner), incoming have border-bottom-left-radius: 4px. This is the standard chat bubble tail pattern.

The typing indicator

Before the simulated reply arrives, three animated dots appear (.typing-indicator) using the same bounce animation as the Dots Loader snippet in this library. The indicator is removed when the reply message is appended.

Customising for real use

Wire sendMessage() to a WebSocket or API endpoint. Render incoming messages from the server's message event. For a real chat UI, each message needs a sender ID, timestamp, and message ID for proper rendering and ordering.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Rather than assuming the auto-scroll and bubble styling are trivial, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why setting scrollTop to scrollHeight reliably scrolls to the bottom regardless of how many messages exist, and why the outgoing and incoming bubbles use opposite asymmetric border-radius corners instead of a uniform rounded rectangle. The same assistant can help optimize it — ask whether appending raw text into innerHTML in sendMsg() is safe if a user types HTML-like characters, and how you'd escape it properly without losing the ability to render rich content like links. It's also a good partner for extending the UI: ask it to add a typing indicator that appears before the simulated reply, wire sendMsg() to a real WebSocket connection instead of the setTimeout-based fake reply, or add read-receipt checkmarks that update from single to double tick asynchronously. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "chat bubble UI" in plain HTML, CSS, and JavaScript — no framework, no library.

Requirements:
- A fixed-width chat panel with a header showing an avatar, an online status dot, a name and status line, followed by a scrollable message list and a bottom input row with a text field and a send button.
- Every message must be a div with a shared base class plus either an "incoming" or "outgoing" modifier class: outgoing messages align to the right with an accent-colored bubble, incoming messages align to the left with a neutral bubble, and both must be capped at a maximum width (not full container width) so short messages don't stretch edge to edge.
- Give outgoing and incoming bubbles opposite asymmetric corner radii (a small "tail" corner on the side facing the edge of the screen) so they visually read as speech bubbles pointing toward their sender's side.
- Sending a message must read and trim the input's value, do nothing if it's empty, create a new outgoing message element with the trimmed text and a formatted current timestamp, append it to the message list, clear the input, and scroll the message container to its maximum scroll position so the new message is always visible.
- Pressing the Enter key while focused in the text input must trigger the exact same send function as clicking the send button — no duplicated logic between the two paths.
- After an outgoing message is sent, simulate a delayed incoming reply (via a timeout of under a second) that appends its own incoming-styled message and re-scrolls the container to the bottom.

Step by step

How to Use

  1. 1
    Type and send a messageType in the input and press Enter or click Send. Your message appears as an outgoing bubble. The timestamp shows the current time.
  2. 2
    Update the chat headerIn the HTML panel, change the contact name and status in .chat-header.
  3. 3
    Add incoming messagesIn the JS panel, add an incoming message after sending: append a .msg.incoming div to mimic a reply.
  4. 4
    Change bubble coloursUpdate background on .msg.outgoing (accent) and .msg.incoming (grey) in the CSS panel.
  5. 5
    Connect to a WebSocketReplace the static incoming message placeholder with a WebSocket onmessage handler that appends .msg.incoming divs from server data.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Customer support chat widget
Use as the UI for an embedded support chat. Wire sendMsg() to a WebSocket or third-party chat API like Intercom or Crisp.
AI chatbot interface
Use as the UI for an AI assistant. Send user messages to an API endpoint and append incoming bubble replies from the response.
Social and messaging apps
Prototype a direct message UI. The outgoing/incoming bubble pattern is identical to iMessage, WhatsApp, and Slack direct messages.
Learn DOM append and auto-scroll
Edit the sendMsg function to understand how createElement, innerHTML, appendChild, and scrollTop work together to create a live-updating list.
Chatbot onboarding flows
Simulate a guided onboarding conversation. Programmatically append incoming messages on a timer to create an interactive step-by-step flow.
Chat UI for design presentations
Use as a static mockup by pre-populating the message list in the HTML panel. The chat bubble layout communicates conversation context clearly.

Got questions?

Frequently Asked Questions

sendMsg() calls document.createElement("div"), sets el.className = "msg outgoing", sets el.innerHTML with the text and timestamp, appends it to #msgs, then sets msgs.scrollTop = msgs.scrollHeight to scroll to the new message.

msgs.scrollTop = msgs.scrollHeight sets the scroll position to the maximum possible value — the total height of all messages. This is called after every append, keeping the latest message visible.

In sendMsg(), after appending the outgoing message, create a second element with className = "msg incoming" and simulate a reply: setTimeout(() => { const reply = document.createElement("div"); reply.className = "msg incoming"; reply.innerHTML = "<p>Got it!</p>"; msgs.append(reply); msgs.scrollTop = msgs.scrollHeight; }, 1000); }

Open a WebSocket connection: const ws = new WebSocket("wss://your-server.com"). In sendMsg(), call ws.send(text) instead of immediately appending. In ws.onmessage, append .msg.incoming divs with the received data.

For emoji, add an emoji picker button that inserts characters into the input value. For files, add a <input type="file"> button. On file selection, read with FileReader and display as an <img> in the bubble.

Yes. Click "JSX" for a React component. In React, manage messages as an array in useState. Use useRef on the message container and call ref.current.scrollTo({ top: ref.current.scrollHeight }) in a useEffect after messages update.