Mobile Chat Screen — Free HTML CSS JS UI Snippet

Mobile Chat Screen · Mobile · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Two-sided bubbles
One class, alignment flipped per row with flex.
CSS bubble tails
Cut corner radius fakes the tail — no SVG.
Animated typing dots
Staggered keyframes with per-dot delays.
Read receipts
Double-tick ticks on outgoing messages.
Smart send button
Disabled while the input is empty.
Auto-scroll
Thread jumps to the newest message on send.
Bubble entrance
Each message fades and lifts into place.
No dependency
Pure HTML, CSS, and vanilla JavaScript.

About this UI Snippet

Mobile Chat Screen — Messaging Thread UI

Screenshot of the Mobile Chat Screen snippet rendered live

A chat screen is the core of every messaging app — a header with the contact and presence, a scrolling thread of left- and right-aligned bubbles, a typing indicator, and a composer bar pinned to the bottom. This snippet builds a complete, interactive one inside a CSS phone frame: you can type a message, watch it appear as an outgoing bubble, see an animated typing indicator, and get a simulated reply — all in HTML, CSS, and vanilla JavaScript with no dependency.

The two-sided bubble layout

Incoming and outgoing messages share one .mc-bubble class but flip alignment through their row: .mc-row.out uses justify-content: flex-end to push the bubble right, while incoming rows stay left. The tail is faked by rounding three corners fully and cutting the fourth to a small radius — border-bottom-left-radius for incoming, border-bottom-right-radius for outgoing — the universal chat-bubble shape without any SVG. Outgoing bubbles get a gradient fill and white text; incoming bubbles are white with a soft shadow.

The typing indicator

Three dots animate with a staggered @keyframes mcBounce, each dot delayed by 150ms so they ripple. The indicator is toggled with the hidden attribute rather than being rebuilt, and the header presence text swaps to "typing…" while it shows — the two cues you get in real apps when the other person is composing.

The composer and send state

The input is a real <form>, so pressing Enter submits. The send button is disabled whenever the trimmed input is empty and re-enables as you type, which prevents sending blank messages and mirrors native behavior. On submit, the message is appended as an outgoing bubble with double-tick read receipts, the thread auto-scrolls to the bottom via scrollTop = scrollHeight, and a timed reply fires after the typing indicator.

Bubble entrance animation

Every new bubble runs a short mcPop keyframe that fades and lifts it into place, so messages feel like they land rather than blink in. Because bubbles are appended to a flex column, the layout reflows naturally and the newest message always sits at the bottom.

Accessibility and performance

The composer is a real <form> with a labelled text input and an aria-label on the send button, so keyboard users can type and press Enter to send, and screen readers announce the control's purpose. The read-receipt ticks are decorative and carried inside the bubble text rather than as separate imagery, so they add no announcement noise. Performance-wise the thread is a plain flex column that appends one node per message and never re-renders the whole list, and the auto-scroll is a single scrollTop assignment rather than a layout-thrashing loop. The typing indicator animates purely in CSS, so it costs nothing on the main thread while it runs, and toggling it with the hidden attribute avoids rebuilding DOM. For very long conversations you would virtualize the thread so only the visible bubbles are mounted, but for a typical screen's worth of messages the direct-append approach stays smooth and keeps the code readable.

Reusing it

Replace the seeded messages and the canned replies array with your real data, and swap the setTimeout for a WebSocket or fetch call that streams responses. Lift the thread out of the phone frame for a responsive web chat, or keep it framed next to a mobile chat conversation list to present a full inbox-to-thread flow.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not have to reconstruct the bubble logic from scratch to understand it. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to walk through exactly how the addBubble function decides which corner radius to cut for the tail, or why the send button's disabled state is tied to input.value.trim() rather than the raw value. The same assistant is useful for optimizing it — ask whether appending one bubble at a time versus batching DOM writes matters once a thread has hundreds of messages, or how you would virtualize the mc-thread container so only visible bubbles stay mounted. It is just as useful for extending the effect: have it add message reactions, image attachments through the plus button, or swap the canned replies array and setTimeout for a real WebSocket connection. 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 mobile messaging thread screen in plain HTML, CSS, and JavaScript inside a phone-frame container — no chat library, no build step.

Requirements:
- A header with a back button, an avatar with an online-status dot, a name plus a presence label, and a call button.
- A scrolling thread area where incoming and outgoing messages share one bubble class but flip alignment by wrapping each bubble in a row that is either left-aligned or uses justify-content flex-end for outgoing messages.
- Fake the bubble tail with border radius alone: fully round three corners and cut the fourth to a small radius, using the opposite corner for incoming versus outgoing bubbles, with no images or SVGs.
- A real form element wrapping a text input and a send button, where the send button is disabled whenever the trimmed input value is empty and re-enables on the input event as the user types.
- On submit, append the message as an outgoing bubble with a double-tick read receipt, clear the input, then show a three-dot typing indicator built from staggered CSS keyframe animations (each dot delayed slightly more than the last) while the header presence label temporarily changes to a typing state.
- After a short delay, hide the typing indicator, restore the presence label, and append a simulated reply cycling through a small array of canned messages.
- Every new bubble should play a brief entrance animation (fade plus a small upward translate) and the thread must auto-scroll to the bottom after every append by setting scrollTop to scrollHeight.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA chat thread renders with seeded incoming and outgoing bubbles inside a phone frame.
  2. 2
    Type a messageThe send button lights up as soon as the input has text.
  3. 3
    Send itYour message appears as a gradient outgoing bubble with read ticks and the thread scrolls down.
  4. 4
    Watch the typing indicatorThree bouncing dots appear and the header shows "typing…" before a reply lands.
  5. 5
    Keep the conversation goingEach send cycles through the canned replies so the thread grows.
  6. 6
    Wire your backendSwap the timed reply for a WebSocket or fetch call to make it live.

Real-world uses

Common Use Cases

Messaging apps
The thread view behind a chat conversation list.
Support widgets
A framed take on a floating chat widget.
AI assistants
Style responses like an AI chat interface.
Presence cues
Reuse the dots as a typing indicator elsewhere.
App mockups
Drop it into a phone mockup for a pitch.
Learning chat UIs
A reference for bubble alignment and auto-scroll.

Got questions?

Frequently Asked Questions

Both use the same .mc-bubble class, but each sits in a row that controls alignment. Incoming rows stay left; outgoing rows use justify-content: flex-end to push the bubble to the right edge. The tail is faked by fully rounding three corners and cutting the fourth — bottom-left for incoming, bottom-right for outgoing.

It is a simulation for the demo: on send, the header swaps to "typing…", the three-dot indicator un-hides, and after a short timeout a canned reply is appended. In production you would show the indicator when your server signals the other user is composing, and append the message when it actually arrives.

The button is disabled whenever the trimmed input value is empty and re-enables on the input event as you type. This prevents sending blank messages and matches how native messaging apps grey out send until there is content.

After each new bubble is appended, the code sets thread.scrollTop = thread.scrollHeight, which jumps the scroll container to the bottom. Because bubbles live in a flex column, the newest message is always the last child, so this keeps the latest message in view.

Render bubbles from a messages array and key them by id. In React append with a state setter and scroll to the bottom in a useEffect that watches the array; in Vue use a ref and nextTick; in Angular use ngAfterViewChecked. Replace the setTimeout reply with your WebSocket or fetch handler. The CSS ports directly, and Tailwind can express the bubble radii and gradient with utilities.

Yes. The thread already includes a centered day divider, and you can insert more by comparing each message's timestamp to the previous one and adding a divider when the date changes. To collapse consecutive messages from the same sender into a group, check whether the previous bubble shares the direction and only render the avatar or tail on the last bubble in the run, which is how most messaging apps tighten long exchanges.